DateFormatter parsing Timezone inconsistently

**Describe the bug**
There is a platform difference with the timezone parsed by a DateFormatter.
On Android, the DateFormatter parses into the timezone of the date string, regardless of the timezone set by the formatter. Moreover, the formatter will change its timezone into the parsed timezone.

On iOS, the timezone is entirely ignored and the date is parsed into the default timezone.

This inconsistency should be resolved. Since the Kaluga Date object contains the TimeZone as an integral component, the proposed solution is to parse into the timezone set by the formatter (as opposed by the parsed string). This is partially because iOS does not seem to support parsing the timezone of the string, and partially because it makes the most sense when Date is in a timezone.

**To Reproduce**
```
val utcFormatter = DateFormatter.patternFormat("yyyy.MM.dd G 'at' HH:mm:ss z", TimeZone.utc, Locale.enUsPosix)
val pstFormatter = DateFormatter.patternFormat("yyyy.MM.dd G 'at' HH:mm:ss z", PSTTimeZone, Locale.enUsPosix)

val epochInUtc = Date.epoch(timeZone = TimeZone.utc, locale = Locale.enUsPosix)
val epochInPst = Date.epoch(timeZone = PSTTimeZone, locale = Locale.enUsPosix)
assertEquals("1970.01.01 AD at 00:00:00 ${TimeZone.utc.identifier}", utcFormatter.format(epochInUtc))
assertEquals("1970.01.01 AD at 00:00:00 ${TimeZone.utc.identifier}", utcFormatter.format(epochInPst))
assertEquals("1969.12.31 AD at 16:00:00 PST", pstFormatter.format(epochInUtc))
assertEquals("1969.12.31 AD at 16:00:00 PST", pstFormatter.format(epochInPst))
assertEquals(epochInUtc, utcFormatter.parse(utcFormatter.format(epochInUtc)))
assertEquals(epochInUtc, utcFormatter.parse(pstFormatter.format(epochInPst)))
assertEquals(epochInPst, pstFormatter.parse(utcFormatter.format(epochInUtc)))
assertEquals(epochInPst, pstFormatter.parse(pstFormatter.format(epochInPst)))
```
This code fails

**Expected behavior**
Parse into the timezone of the parser

diff --git a/base/src/androidLibMain/kotlin/text/DateFormatter.kt b/base/src/androidLibMain/kotlin/text/DateFormatter.kt
index 6126e836d..d7f118c2e 100644
--- a/base/src/androidLibMain/kotlin/text/DateFormatter.kt
+++ b/base/src/androidLibMain/kotlin/text/DateFormatter.kt
@@ -124,13 +124,22 @@ actual class DateFormatter private constructor(private val format: SimpleDateFor

     actual fun format(date: Date): String = format.format(date.calendar.time)
     actual fun parse(string: String): Date? {
+        val currentTimeZone = timeZone
         return try {
             format.parse(string)?.let { date ->
                 val calendar = format.calendar.clone() as Calendar
-                Date(calendar.apply { time = date })
+                Date(
+                    calendar.apply {
+                        time = date
+                        timeZone = currentTimeZone.timeZone
+                    }
+                )
             }
         } catch (e: ParseException) {
             null
+        } finally {
+            // Parse may change the timezone to the timezone parsed by the String. This restores the original timezone
+            timeZone = currentTimeZone
         }
     }

diff --git a/base/src/commonTest/kotlin/text/DateFormatterTest.kt b/base/src/commonTest/kotlin/text/DateFormatterTest.kt
index 6881fbcc5..8aa801cca 100644
--- a/base/src/commonTest/kotlin/text/DateFormatterTest.kt
+++ b/base/src/commonTest/kotlin/text/DateFormatterTest.kt
@@ -22,8 +22,10 @@ import com.splendo.kaluga.base.text.DateFormatter
 import com.splendo.kaluga.base.text.dateFormat
 import com.splendo.kaluga.base.text.iso8601Pattern
 import com.splendo.kaluga.base.utils.Date
+import com.splendo.kaluga.base.utils.Locale
 import com.splendo.kaluga.base.utils.Locale.Companion.createLocale
 import com.splendo.kaluga.base.utils.TimeZone
+import com.splendo.kaluga.base.utils.enUsPosix
 import com.splendo.kaluga.base.utils.nowUtc
 import com.splendo.kaluga.base.utils.utc
 import kotlin.test.Test
@@ -64,6 +66,10 @@ class DateFormatterTest {
         assertEquals(2020, date.year)
         assertEquals(1, date.month)
         assertEquals(8, date.day)
+        assertEquals(0, date.hour)
+        assertEquals(0, date.minute)
+        assertEquals(0, date.second)
+        assertEquals(PSTTimeZone.identifier, date.timeZone.identifier)
     }

     @Test
@@ -104,4 +110,21 @@ class DateFormatterTest {
         val formatter = DateFormatter.iso8601Pattern(TimeZone.utc)
         assertNull(formatter.parse("invalid date"))
     }
+
+    @Test
+    fun testParseDateWithDifferentTimezone() {
+        val utcFormatter = DateFormatter.patternFormat("yyyy.MM.dd G 'at' HH:mm:ss z", TimeZone.utc, Locale.enUsPosix)
+        val pstFormatter = DateFormatter.patternFormat("yyyy.MM.dd G 'at' HH:mm:ss z", PSTTimeZone, Locale.enUsPosix)
+
+        val epochInUtc = Date.epoch(timeZone = TimeZone.utc, locale = Locale.enUsPosix)
+        val epochInPst = Date.epoch(timeZone = PSTTimeZone, locale = Locale.enUsPosix)
+        assertEquals("1970.01.01 AD at 00:00:00 ${TimeZone.utc.identifier}", utcFormatter.format(epochInUtc))
+        assertEquals("1970.01.01 AD at 00:00:00 ${TimeZone.utc.identifier}", utcFormatter.format(epochInPst))
+        assertEquals("1969.12.31 AD at 16:00:00 PST", pstFormatter.format(epochInUtc))
+        assertEquals("1969.12.31 AD at 16:00:00 PST", pstFormatter.format(epochInPst))
+        assertEquals(epochInUtc, utcFormatter.parse(utcFormatter.format(epochInUtc)))
+        assertEquals(epochInUtc, utcFormatter.parse(pstFormatter.format(epochInPst)))
+        assertEquals(epochInPst, pstFormatter.parse(utcFormatter.format(epochInUtc)))
+        assertEquals(epochInPst, pstFormatter.parse(pstFormatter.format(epochInPst)))
+    }
 }
diff --git a/base/src/iosMain/kotlin/text/DateFormatter.kt b/base/src/iosMain/kotlin/text/DateFormatter.kt
index 60d4c3442..2501cafe2 100644
--- a/base/src/iosMain/kotlin/text/DateFormatter.kt
+++ b/base/src/iosMain/kotlin/text/DateFormatter.kt
@@ -21,6 +21,7 @@ import com.splendo.kaluga.base.typedList
 import com.splendo.kaluga.base.utils.Date
 import com.splendo.kaluga.base.utils.Locale
 import com.splendo.kaluga.base.utils.TimeZone
+import com.splendo.kaluga.base.utils.utc
 import platform.Foundation.NSCalendar
 import platform.Foundation.NSDateFormatter
 import platform.Foundation.NSDateFormatterFullStyle
@@ -56,6 +57,16 @@ actual class DateFormatter private constructor(private val format: NSDateFormatt
             NSDateFormatter().apply {
                 this.locale = locale.nsLocale
                 this.timeZone = timeZone.timeZone
+                this.defaultDate = Date.now(timeZone = timeZone).apply {
+                    val epoch = Date.epoch(timeZone = TimeZone.utc)
+                    this.era = epoch.era
+                    this.year = epoch.year
+                    this.month = epoch.month
+                    this.day = epoch.day
+                    this.hour = epoch.hour
+                    this.minute = epoch.minute
+                    this.second = epoch.second
+                }.date
                 dateFormat = pattern
             }
         )
@@ -69,6 +80,16 @@ actual class DateFormatter private constructor(private val format: NSDateFormatt
             NSDateFormatter().apply {
                 this.locale = locale.nsLocale
                 this.timeZone = timeZone.timeZone
+                this.defaultDate = Date.now(timeZone = timeZone).apply {
+                    val epoch = Date.epoch(timeZone = TimeZone.utc)
+                    this.era = epoch.era
+                    this.year = epoch.year
+                    this.month = epoch.month
+                    this.day = epoch.day
+                    this.hour = epoch.hour
+                    this.minute = epoch.minute
+                    this.second = epoch.second
+                }.date
                 this.dateStyle = dateStyle.nsDateFormatterStyle()
                 this.timeStyle = timeStyle.nsDateFormatterStyle()
             }
@@ -112,6 +133,7 @@ actual class DateFormatter private constructor(private val format: NSDateFormatt
     actual fun parse(string: String): Date? {
         return format.dateFromString(string)?.let { date ->
             val calendar = format.calendar.copy() as NSCalendar
+            calendar.timeZone = timeZone.timeZone
             Date(calendar, date)
         }
     }
diff --git a/base/src/jvmMain/kotlin/text/DateFormatter.kt b/base/src/jvmMain/kotlin/text/DateFormatter.kt
index 4b3ac8b56..53d0e9835 100644
--- a/base/src/jvmMain/kotlin/text/DateFormatter.kt
+++ b/base/src/jvmMain/kotlin/text/DateFormatter.kt
@@ -127,13 +127,22 @@ actual class DateFormatter private constructor(private val format: SimpleDateFor

     actual fun format(date: Date): String = format.format(date.calendar.time)
     actual fun parse(string: String): Date? {
+        val currentTimeZone = timeZone
         return try {
             format.parse(string)?.let { date ->
                 val calendar = format.calendar.clone() as Calendar
-                Date(calendar.apply { time = date })
+                Date(
+                    calendar.apply {
+                        time = date
+                        timeZone = currentTimeZone.timeZone
+                    }
+                )
             }
         } catch (e: ParseException) {
             null
+        } finally {
+            // Parse may change the timezone to the timezone parsed by the String. This restores the original timezone
+            timeZone = currentTimeZone
         }
     }

DateFormatter parsing Timezone inconsistently
**Describe the bug**
There is a platform difference with the timezone parsed by a DateFormatter.
On Android, the DateFormatter parses into the timezone of the date string, regardless of the timezone set by the formatter. Moreover, the formatter will change its timezone into the parsed timezone.

On iOS, the timezone is entirely ignored and the date is parsed into the default timezone.

This inconsistency should be resolved. Since the Kaluga Date object contains the TimeZone as an integral component, the proposed solution is to parse into the timezone set by the formatter (as opposed by the parsed string). This is partially because iOS does not seem to support parsing the timezone of the string, and partially because it makes the most sense when Date is in a timezone.

**To Reproduce**
```
val utcFormatter = DateFormatter.patternFormat("yyyy.MM.dd G 'at' HH:mm:ss z", TimeZone.utc, Locale.enUsPosix)
val pstFormatter = DateFormatter.patternFormat("yyyy.MM.dd G 'at' HH:mm:ss z", PSTTimeZone, Locale.enUsPosix)

val epochInUtc = Date.epoch(timeZone = TimeZone.utc, locale = Locale.enUsPosix)
val epochInPst = Date.epoch(timeZone = PSTTimeZone, locale = Locale.enUsPosix)
assertEquals("1970.01.01 AD at 00:00:00 ${TimeZone.utc.identifier}", utcFormatter.format(epochInUtc))
assertEquals("1970.01.01 AD at 00:00:00 ${TimeZone.utc.identifier}", utcFormatter.format(epochInPst))
assertEquals("1969.12.31 AD at 16:00:00 PST", pstFormatter.format(epochInUtc))
assertEquals("1969.12.31 AD at 16:00:00 PST", pstFormatter.format(epochInPst))
assertEquals(epochInUtc, utcFormatter.parse(utcFormatter.format(epochInUtc)))
assertEquals(epochInUtc, utcFormatter.parse(pstFormatter.format(epochInPst)))
assertEquals(epochInPst, pstFormatter.parse(utcFormatter.format(epochInUtc)))
assertEquals(epochInPst, pstFormatter.parse(pstFormatter.format(epochInPst)))
```
This code fails

**Expected behavior**
Parse into the timezone of the parser

/*
Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.alerts

import android.app.AlertDialog
import android.content.Context
import android.text.Editable
import android.text.InputType
import android.text.TextWatcher
import android.view.ViewGroup
import android.widget.EditText
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import com.splendo.kaluga.architecture.lifecycle.LifecycleManagerObserver
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable
import com.splendo.kaluga.architecture.lifecycle.getOrPutAndRemoveOnDestroyFromCache
import com.splendo.kaluga.architecture.lifecycle.lifecycleManagerObserver
import com.splendo.kaluga.base.utils.applyIf
import com.splendo.kaluga.resources.dpToPixel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch

actual class AlertPresenter(
    private val alert: Alert,
    private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver(),
    coroutineScope: CoroutineScope
) : BaseAlertPresenter(alert), CoroutineScope by coroutineScope {

    actual class Builder(
        private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver()
    ) : BaseAlertPresenter.Builder(), LifecycleSubscribable by lifecycleManagerObserver {
        actual override fun create(coroutineScope: CoroutineScope) =
            AlertPresenter(createAlert(), lifecycleManagerObserver, coroutineScope)
    }

    private companion object {
        fun transform(style: Alert.Action.Style): Int = when (style) {
            Alert.Action.Style.DEFAULT, Alert.Action.Style.POSITIVE -> AlertDialog.BUTTON_POSITIVE
            Alert.Action.Style.DESTRUCTIVE, Alert.Action.Style.NEUTRAL -> AlertDialog.BUTTON_NEUTRAL
            Alert.Action.Style.CANCEL, Alert.Action.Style.NEGATIVE -> AlertDialog.BUTTON_NEGATIVE
        }
    }

    private sealed class DialogPresentation {
        data class Showing(
            val animated: Boolean,
            val afterHandler: (Alert.Action?) -> Unit,
            val completion: () -> Unit
        ) : DialogPresentation()

        object Hidden : DialogPresentation()
    }

    private val presentation = MutableStateFlow<DialogPresentation>(DialogPresentation.Hidden)
    private var alertDialog: AlertDialog? = null

    init {
        launch {
            combine(
                lifecycleManagerObserver.managerState,
                presentation
            ) { managerState, dialogPresentation ->
                Pair(managerState, dialogPresentation)
            }.collect { contextPresentation ->
                when (val dialogPresentation = contextPresentation.second) {
                    is DialogPresentation.Showing -> contextPresentation.first?.activity?.let {
                        presentDialog(it, dialogPresentation)
                    } ?: run { alertDialog = null }
                    is DialogPresentation.Hidden -> alertDialog?.dismiss()
                }
            }
        }
    }

    override fun dismissAlert(animated: Boolean) {
        presentation.value = DialogPresentation.Hidden
    }

    override fun showAlert(
        animated: Boolean,
        afterHandler: (Alert.Action?) -> Unit,
        completion: () -> Unit
    ) {
        presentation.value = DialogPresentation.Showing(animated, afterHandler, completion)
    }

    private fun presentDialog(context: Context, presentation: DialogPresentation.Showing) {
        alertDialog = AlertDialog.Builder(context)
            .setTitle(alert.title)
            .setMessage(alert.message)
            .applyIf(alert.style == Alert.Style.ACTION_LIST) {
                val titles = alert.actions.map { it.title }.toTypedArray()
                setItems(titles) { _, which ->
                    val action = alert.actions[which].apply { handler() }
                    presentation.afterHandler(action)
                }
            }
            .create()
            .applyIf(alert.style == Alert.Style.TEXT_INPUT) {
                alert.textInputAction?.let { textInputAction ->
                    val editText = createEditTextView(
                        context,
                        textInputAction
                    )
                    setView(editText)
                }
                alert.actions.forEach { action ->
                    setButton(transform(action.style), action.title) { _, _ ->
                        action.handler()
                        presentation.afterHandler(action)
                    }
                }
            }
            .applyIf(alert.style == Alert.Style.ALERT) {
                alert.actions.forEach { action ->
                    setButton(transform(action.style), action.title) { _, _ ->
                        action.handler()
                        presentation.afterHandler(action)
                    }
                }
            }
            .apply {
                setOnDismissListener {
                    alertDialog = null
                    this@AlertPresenter.presentation.value = DialogPresentation.Hidden
                }
                setOnCancelListener { presentation.afterHandler(null) }
                show()
            }
        presentation.completion()
    }

    /**
     * Creates an [EditText] wrapped in a [LinearLayout] for dialogs of type [Alert.Style.TEXT_INPUT]
     *
     * @param context The Android context used to create view
     * @param textInputAction The [Alert.TextInputAction] used for initializing the [EditText]
     */
    private fun createEditTextView(
        context: Context,
        textInputAction: Alert.TextInputAction
    ): LinearLayout {
        val linearLayout = LinearLayout(context)
        val layoutParams = LinearLayout.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
        )
        val editText = EditText(context)
        editText.layoutParams = layoutParams
        linearLayout.addView(editText)
        val padding =
            context.resources.getDimension(R.dimen.dialog_text_input_padding).dpToPixel(context).toInt()
        linearLayout.setPaddingRelative(padding, 0, padding, 0)
        editText.inputType = InputType.TYPE_CLASS_TEXT
        editText.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(
                s: CharSequence?,
                start: Int,
                count: Int,
                after: Int
            ) {
                // Do nothing
            }

            override fun onTextChanged(
                s: CharSequence?,
                start: Int,
                before: Int,
                count: Int
            ) {
                // Do nothing
            }

            override fun afterTextChanged(s: Editable?) {
                textInputAction.textObserver(s.toString())
            }
        })
        textInputAction.placeholder?.let { editText.hint = it }
        textInputAction.text?.let { editText.setText(it) }
        return linearLayout
    }
}

/**
 * @return The [AlertPresenter.Builder] which can be used to present alerts while this Activity is active
 * Will be created if need but only one instance will exist.
 *
 * Warning: Do not attempt to use this builder outside of the lifespan of the Activity.
 * Instead, for example use a [com.splendo.kaluga.architecture.viewmodel.ViewModel],
 * which can automatically track which Activity is active for it.
 *
 */
fun AppCompatActivity.alertPresenterBuilder(): AlertPresenter.Builder =
    getOrPutAndRemoveOnDestroyFromCache {
        AlertPresenter.Builder(lifecycleManagerObserver())
    }

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.alerts

import co.touchlab.stately.concurrency.Lock
import co.touchlab.stately.concurrency.withLock
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume

typealias AlertActionHandler = () -> Unit
typealias AlertTextObserver = (String) -> Unit

/**
 * An object that represents an alert with title and/or message and actions (button)
 *
 * @property title The title of the alert
 * @property message The descriptive text that provides more details
 * @property actions The list of action objects that the user can take in response to the alert
 * @property textInputAction The optional action object that sets the alert's input options and text change callback
 */
data class Alert(
    val title: String?,
    val message: String?,
    val actions: List<Action>,
    val textInputAction: TextInputAction? = null,
    val style: Style = Style.ALERT
) {

    /**
     * Alert style
     */
    enum class Style {
        ALERT, ACTION_LIST, TEXT_INPUT
    }

    /**
     * An action that represents a button in the alert
     *
     * @property title The title of the action's button
     * @property style The style that is applied to the action's button
     * @property handler The block to execute when the user taps a button
     */
    data class Action(
        val title: String,
        val style: Style = Style.DEFAULT,
        val handler: AlertActionHandler = {}
    ) {
        enum class Style(val value: Int) {
            DEFAULT(0),
            POSITIVE(DEFAULT.value),
            DESTRUCTIVE(1),
            NEUTRAL(DESTRUCTIVE.value),
            CANCEL(2),
            NEGATIVE(CANCEL.value)
        }
    }

    /**
     * An action that represents an input field in the alert and its initial state
     *
     * @param text The initial text of the input field
     * @param placeholder The hint of the input field
     * @param textObserver The block to execute when the user edits the text in the input field
     */
    data class TextInputAction(
        val text: String?,
        val placeholder: String?,
        val textObserver: AlertTextObserver
    )
}

/**
 * Interface that defines actions that can be applied to the alert.
 */
interface AlertActions {
    /**
     * Presents an alert
     *
     * @param animated Pass `true` to animate the presentation
     * @param completion The block to execute after the presentation finishes
     */
    fun showAsync(animated: Boolean = true, completion: () -> Unit = {})

    /**
     * Presents an alert and suspends
     *
     * @param animated
     * @return The action that was performed by button click
     *         or `null` if the alert was cancelled by pressing back (on Android)
     */
    suspend fun show(animated: Boolean = true): Alert.Action?

    /**
     * Dismisses the alert, which was presented previously
     *
     * @param animated Pass `true` to animate the transition
     */
    fun dismiss(animated: Boolean = true)
}

/**
 * Abstract alert presenter, used to show and dismiss given [Alert]
 * @see [AlertPresenter]
 *
 * @property alert The alert to present (and dismiss if needed)
 */
abstract class BaseAlertPresenter(private val alert: Alert) : AlertActions {

    /**
     * Abstract alert builder class, used to create an [Alert].
     * The resulting Alert that can be shown and dismissed using an [AlertPresenter].
     *
     * @see [AlertPresenter.Builder]
     */
    abstract class Builder : LifecycleSubscribableMarker {

        private var title: String? = null
        private var message: String? = null
        private var actions: MutableList<Alert.Action> = mutableListOf()
        private var textInputAction: Alert.TextInputAction? = null
        private var style: Alert.Style = Alert.Style.ALERT
        internal val lock = Lock()

        /**
         * Sets the [title] displayed in the alert
         *
         * @param title The title of the alert
         */
        fun setTitle(title: String?) = apply { this.title = title }

        /**
         * Sets the [message] displayed in the alert
         *
         * @param message The message of the alert
         */
        fun setMessage(message: String?) = apply { this.message = message }

        /**
         * Sets button with the id `BUTTON_POSITIVE` on Android
         * and action with style `UIAlertActionStyleDefault` on iOS
         *
         * @param title The title of the button
         * @param handler The block to execute after user taps a button
         */
        fun setPositiveButton(title: String, handler: AlertActionHandler = {}) = apply {
            addAction(Alert.Action(title, Alert.Action.Style.POSITIVE, handler))
        }

        /**
         * Sets button with the id `BUTTON_NEGATIVE` on Android
         * and action with style `UIAlertActionStyleCancel` on iOS
         *
         * @param title The title of the button
         * @param handler The block to execute after user taps a button
         */
        fun setNegativeButton(title: String, handler: AlertActionHandler = {}) = apply {
            addAction(Alert.Action(title, Alert.Action.Style.NEGATIVE, handler))
        }

        /**
         * Sets button with the id `BUTTON_NEUTRAL` on Android
         * and action with style `UIAlertActionStyleDestructive` on iOS
         *
         * @param title The title of the button
         * @param handler The block to execute after user taps a button
         */
        fun setNeutralButton(title: String, handler: AlertActionHandler = {}) = apply {
            addAction(Alert.Action(title, Alert.Action.Style.NEUTRAL, handler))
        }

        /**
         * Initializes alert's input field
         *
         * @param text The initial text of the input field
         * @param placeholder The input field hint
         * @param textObserver The callback for text change events of inout field
         */
        fun setTextInput(
            text: String? = null,
            placeholder: String?,
            textObserver: AlertTextObserver
        ) = apply {
            setTextInputAction(Alert.TextInputAction(text, placeholder, textObserver))
        }

        /**
         * Adds a list of [actions] to the alert
         *
         * @param actions The list of action objects
         */
        fun addActions(actions: List<Alert.Action>) = apply { this.actions.addAll(actions) }

        /**
         * Adds a list of [actions] to the alert
         *
         * @param actions The list of action objects
         */
        fun addActions(vararg actions: Alert.Action) = apply { this.actions.addAll(actions) }

        /**
         * Sets a style of the alert
         *
         * @param style The style of an alert
         */
        internal fun setStyle(style: Alert.Style) = apply { this.style = style }

        /**
         * Adds an [action] to the alert
         *
         * @param action The action object
         */
        private fun addAction(action: Alert.Action) = apply { this.actions.add(action) }

        /**
         * Adds an [Alert.TextInputAction] to the alert
         *
         * @param action The action object
         */
        private fun setTextInputAction(action: Alert.TextInputAction) =
            apply { this.textInputAction = action }

        /**
         * Reset builder into initial state
         */
        internal fun reset() = apply {
            this.title = null
            this.message = null
            this.actions = mutableListOf()
            this.textInputAction = null
            this.style = Alert.Style.ALERT
        }

        /**
         * Creates an alert based on [title], [message], [actions] and [textInputAction] properties
         *
         * @return The alert object
         * @throws IllegalArgumentException in case missing title and/or message or actions
         */
        protected fun createAlert(): Alert {
            if (style == Alert.Style.ALERT) {
                require(title != null || message != null) { "Please set title and/or message for the Alert" }
            } // Action sheet on iOS can be without title and message

            require(actions.isNotEmpty()) { "Please set at least one Action for the Alert" }

            return Alert(title, message, actions, textInputAction, style)
        }

        /**
         * Creates the [BaseAlertPresenter] described by this builder.
         *
         * @param coroutineScope The [CoroutineScope] managing the alert lifecycle.
         * @return The [BaseAlertPresenter] described by this builder.
         */
        abstract fun create(coroutineScope: CoroutineScope): BaseAlertPresenter
    }

    override fun showAsync(animated: Boolean, completion: () -> Unit) {
        showAlert(animated, completion = completion)
    }

    override suspend fun show(animated: Boolean): Alert.Action? =
        suspendCancellableCoroutine { continuation ->
            continuation.invokeOnCancellation {
                dismissAlert(animated)
                continuation.resume(null)
            }
            showAlert(animated, afterHandler = { continuation.resume(it) })
        }

    override fun dismiss(animated: Boolean) {
        dismissAlert(animated)
    }

    protected abstract fun dismissAlert(animated: Boolean = true)

    protected abstract fun showAlert(
        animated: Boolean = true,
        afterHandler: (Alert.Action?) -> Unit = {},
        completion: () -> Unit = {}
    )
}

/**
 * Class for presenting an [Alert].
 */
expect class AlertPresenter : BaseAlertPresenter {
    class Builder : BaseAlertPresenter.Builder {
        /**
         * Creates an [AlertPresenter]
         *
         * @param coroutineScope The [CoroutineScope] managing the alert lifecycle.
         * @return The created [AlertPresenter]
         */
        override fun create(coroutineScope: CoroutineScope): AlertPresenter
    }
}

/**
 * Builds an alert of type [Alert.Style.ALERT] using DSL syntax (thread safe)
 *
 * @param coroutineScope The [CoroutineScope] managing the alert lifecycle.
 * @param initialize The block to construct an Alert
 * @return The built alert interface object
 */
fun BaseAlertPresenter.Builder.buildAlert(
    coroutineScope: CoroutineScope,
    initialize: BaseAlertPresenter.Builder.() -> Unit
): BaseAlertPresenter = lock.withLock {
    reset()
    setStyle(Alert.Style.ALERT)
    initialize()
    return create(coroutineScope)
}

/**
 * Builds an alert of type [Alert.Style.ACTION_LIST] using DSL syntax (thread safe)
 *
 * @param coroutineScope The [CoroutineScope] managing the alert lifecycle.
 * @param initialize The block to construct an Alert
 * @return The built alert interface object
 */
fun BaseAlertPresenter.Builder.buildActionSheet(
    coroutineScope: CoroutineScope,
    initialize: BaseAlertPresenter.Builder.() -> Unit
): BaseAlertPresenter = lock.withLock {
    reset()
    setStyle(Alert.Style.ACTION_LIST)
    initialize()
    return create(coroutineScope)
}

/**
 * Builds an alert of type [Alert.Style.TEXT_INPUT] using DSL syntax (thread safe)
 *
 * @param coroutineScope The [CoroutineScope] managing the alert lifecycle.
 * @param initialize The block to construct an Alert
 * @return The built alert interface object
 */
fun BaseAlertPresenter.Builder.buildAlertWithInput(
    coroutineScope: CoroutineScope,
    initialize: BaseAlertPresenter.Builder.() -> Unit
): BaseAlertPresenter = lock.withLock {
    reset()
    setStyle(Alert.Style.TEXT_INPUT)
    initialize()
    return create(coroutineScope)
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.alerts

import kotlinx.cinterop.ObjCAction
import kotlinx.coroutines.CoroutineScope
import platform.Foundation.NSString
import platform.Foundation.localizedStringWithFormat
import platform.UIKit.UIAlertAction
import platform.UIKit.UIAlertActionStyle
import platform.UIKit.UIAlertActionStyleCancel
import platform.UIKit.UIAlertActionStyleDefault
import platform.UIKit.UIAlertActionStyleDestructive
import platform.UIKit.UIAlertController
import platform.UIKit.UIAlertControllerStyle
import platform.UIKit.UIAlertControllerStyleActionSheet
import platform.UIKit.UIAlertControllerStyleAlert
import platform.UIKit.UIControlEventEditingChanged
import platform.UIKit.UITextField
import platform.UIKit.UIViewController
import platform.objc.sel_registerName

actual class AlertPresenter(
    private val alert: Alert,
    private val parent: UIViewController
) : BaseAlertPresenter(alert) {

    /** Ref to alert's [UITextField] of type [Alert.Style.TEXT_INPUT] */
    private var textField: UITextField? = null

    /** Callback called from [UITextField] for action of type [UIControlEventEditingChanged]
     * for alerts of type [Alert.Style.TEXT_INPUT]
     *
     * The callback does not pass the text back so we have to extract it from the
     * [textField] ref and invoke the [alert]'s [Alert.TextInputAction.textObserver]
     * */
    @ObjCAction
    fun textDidChange() {
        textField?.text?.let {
            alert.textInputAction?.textObserver?.invoke(it)
        }
    }

    private companion object {
        fun transform(style: Alert.Action.Style): UIAlertActionStyle = when (style) {
            Alert.Action.Style.DEFAULT,
            Alert.Action.Style.POSITIVE,
            Alert.Action.Style.NEUTRAL -> UIAlertActionStyleDefault
            Alert.Action.Style.DESTRUCTIVE -> UIAlertActionStyleDestructive
            Alert.Action.Style.CANCEL,
            Alert.Action.Style.NEGATIVE -> UIAlertActionStyleCancel
        }

        fun transform(style: Alert.Style): UIAlertControllerStyle = when (style) {
            Alert.Style.ALERT -> UIAlertControllerStyleAlert
            Alert.Style.ACTION_LIST -> UIAlertControllerStyleActionSheet
            Alert.Style.TEXT_INPUT -> UIAlertControllerStyleAlert
        }
    }

    actual class Builder(private val viewController: UIViewController) : BaseAlertPresenter.Builder() {
        actual override fun create(coroutineScope: CoroutineScope) = AlertPresenter(createAlert(), viewController)
    }

    override fun dismissAlert(animated: Boolean) {
        parent.dismissModalViewControllerAnimated(animated)
    }

    override fun showAlert(
        animated: Boolean,
        afterHandler: (Alert.Action?) -> Unit,
        completion: () -> Unit
    ) {
        UIAlertController.alertControllerWithTitle(
            alert.title,
            alert.message,
            transform(alert.style)
        ).apply {
            alert.actions.forEach { action ->
                addAction(
                    UIAlertAction.actionWithTitle(
                        action.title,
                        transform(action.style)
                    ) {
                        action.handler()
                        afterHandler(action)
                    }
                )
            }
            val cancelButtonIndex = alert.actions.indexOfFirst {
                (it.style == Alert.Action.Style.CANCEL) or (it.style == Alert.Action.Style.NEGATIVE)
            }
            // If there is no Cancel action inject it by default for alerts if type ACTION_LIST
            if (alert.style == Alert.Style.ACTION_LIST && cancelButtonIndex == -1) {
                addAction(
                    UIAlertAction.actionWithTitle(
                        NSString.localizedStringWithFormat("Cancel"),
                        UIAlertActionStyleCancel
                    ) {
                        afterHandler(null)
                    }
                )
            } else if (alert.style == Alert.Style.TEXT_INPUT) {
                alert.textInputAction?.let { textInputAction ->
                    addTextFieldWithConfigurationHandler { textField ->
                        initializeUITextField(textField, textInputAction)
                    }
                }
            }
        }.run {
            parent.presentViewController(this, animated, completion)
        }
    }

    private fun initializeUITextField(textField: UITextField?, textInputAction: Alert.TextInputAction) {
        textField?.placeholder = textInputAction.placeholder
        textField?.addTarget(
            target = this,
            action = sel_registerName("textDidChange"),
            forControlEvents = UIControlEventEditingChanged
        )
        textInputAction.text?.let {
            textField?.text = it
            textInputAction.textObserver.invoke(it)
        }
        this.textField = textField
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.alerts

import kotlinx.coroutines.CoroutineScope

actual class AlertPresenter(
    alert: Alert
) : BaseAlertPresenter(alert) {

    actual class Builder : BaseAlertPresenter.Builder() {

        actual override fun create(coroutineScope: CoroutineScope): AlertPresenter {
            return AlertPresenter(createAlert())
        }
    }

    override fun showAsync(animated: Boolean, completion: (() -> Unit)) {
        TODO("not implemented")
    }

    override suspend fun show(animated: Boolean): Alert.Action? {
        TODO("not implemented")
    }

    override fun dismiss(animated: Boolean) {
        TODO("not implemented")
    }

    override fun dismissAlert(animated: Boolean) {
        TODO("not implemented")
    }

    override fun showAlert(
        animated: Boolean,
        afterHandler: (Alert.Action?) -> Unit,
        completion: () -> Unit
    ) {
        TODO("not implemented")
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.alerts

import kotlinx.coroutines.CoroutineScope

actual class AlertPresenter(
    alert: Alert
) : BaseAlertPresenter(alert) {

    actual class Builder : BaseAlertPresenter.Builder() {

        actual override fun create(coroutineScope: CoroutineScope): AlertPresenter {
            return AlertPresenter(createAlert())
        }
    }

    override fun showAsync(animated: Boolean, completion: (() -> Unit)) {
        TODO("not implemented")
    }

    override suspend fun show(animated: Boolean): Alert.Action? {
        TODO("not implemented")
    }

    override fun dismiss(animated: Boolean) {
        TODO("not implemented")
    }

    override fun dismissAlert(animated: Boolean) {
        TODO("not implemented")
    }

    override fun showAlert(
        animated: Boolean,
        afterHandler: (Alert.Action?) -> Unit,
        completion: () -> Unit
    ) {
        TODO("not implemented")
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.permissions

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.splendo.kaluga.base.ApplicationHolder
import com.splendo.kaluga.logging.debug
import com.splendo.kaluga.logging.error
import com.splendo.kaluga.logging.warn
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.util.Timer
import java.util.concurrent.ConcurrentHashMap
import kotlin.concurrent.fixedRateTimer

internal enum class AndroidPermissionState {
    GRANTED,
    DENIED,
    DENIED_DO_NOT_ASK;

    companion object {

        /**
         * According to my understanding shouldShowRationalePermissionRationale() method
         * returns false in three cases:
         * 1. If we call this method very first time before asking permission.
         * 2. If user selects "Don't ask again" and deny permission.
         * 3. If the device policy prohibits the app from having that permission.
         *
         * That's the reason we utilize shouldShowRationalePermissionRationale()
         * only from [Activity.onRequestPermissionsResult].
         */
        fun get(activity: Activity, permission: String): AndroidPermissionState {
            val permissionState = ContextCompat.checkSelfPermission(activity, permission)
            if (permissionState == PackageManager.PERMISSION_GRANTED) {
                return GRANTED
            }

            return if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
                DENIED
            } else {
                DENIED_DO_NOT_ASK
            }
        }

        fun get(context: Context, permission: String): AndroidPermissionState {
            val permissionState = ContextCompat.checkSelfPermission(context, permission)

            return if (permissionState == PackageManager.PERMISSION_GRANTED) GRANTED else DENIED
        }
    }
}

/**
 * Convenience class for requesting a [Permission]
 * @param context The context for which to request the [Permission]
 * @param permissionManager The [PermissionManager] managing the requested permission
 * @param permissions List of permissions to request. Should correspond to [Manifest.permission].
 * @param coroutineScope The coroutineScope on which to handle permission requests.
 */
class AndroidPermissionsManager<P : Permission> constructor(
    private val context: Context = ApplicationHolder.applicationContext,
    private val permissionManager: PermissionManager<P>,
    private val permissions: Array<String> = emptyArray(),
    coroutineScope: CoroutineScope = permissionManager
) : CoroutineScope by coroutineScope {

    internal companion object {
        const val TAG = "Permissions"

        val permissionsStates: MutableMap<String, AndroidPermissionState> = ConcurrentHashMap()
    }

    private val filteredPermissionsStates: Map<String, AndroidPermissionState>
        get() = permissionsStates.filterKeys { permissions.contains(it) }
    private var timer: Timer? = null

    /**
     * Starts to request the permissions.
     * Use [startMonitoring] to get notified of the permission change.
     * Calls [PermissionManager.requestPermission] if the permission cannot be granted.
     */
    fun requestPermissions() {
        if (missingPermissionsInManifest().isEmpty()) {
            launch {
                val intent = PermissionsActivity.intent(context, *permissions)
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                context.startActivity(intent)
            }
        } else {
            permissionManager.revokePermission(true)
        }
    }

    private fun missingPermissionsInManifest(): List<String> {
        val pm = context.packageManager

        val missingPermissions = permissions.toMutableList()

        try {
            val packageInfo = pm.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS)

            var declaredPermissions: List<String> = mutableListOf()

            if (packageInfo != null && declaredPermissions.isEmpty()) {
                declaredPermissions = packageInfo.requestedPermissions.toList()
            }

            if (declaredPermissions.isNotEmpty()) {
                missingPermissions.toList().forEach { requestedPermissionName ->
                    if (declaredPermissions.contains(requestedPermissionName)) {
                        debug(TAG, "Permission $requestedPermissionName was declared in manifest")
                        missingPermissions.remove(requestedPermissionName)
                    } else {
                        warn(TAG, "Permission $requestedPermissionName was not declared in manifest")
                    }
                }
            }
        } catch (e: PackageManager.NameNotFoundException) {
            error(TAG, e)
        }

        return missingPermissions
    }

    /**
     * Starts monitoring for changes to the permission.
     * Calls [PermissionManager.grantPermission] if the permission became granted
     * and [PermissionManager.revokePermission] if it became denied.
     * @param interval The interval in milliseconds between checks in changes to the permission state.
     */
    fun startMonitoring(interval: Long) {
        updatePermissionsStates()
        if (timer != null) return
        // TODO use a coroutine bases timer as in iOS
        timer = fixedRateTimer(period = interval) {
            monitor()
        }
    }

    internal fun monitor() {
        updatePermissionsStates()
        if (hasPermissions) {
            permissionManager.grantPermission()
        } else {
            val locked = filteredPermissionsStates.any {
                it.value == AndroidPermissionState.DENIED_DO_NOT_ASK
            }
            permissionManager.revokePermission(locked)
        }
    }

    /**
     * Stops monitoring for changes to the permission.
     */
    fun stopMonitoring() {
        timer?.cancel()
        timer = null
    }

    /**
     * `true` if the permission has been granted.
     */
    val hasPermissions: Boolean
        get() = filteredPermissionsStates.values.all { it == AndroidPermissionState.GRANTED }

    private fun updatePermissionsStates() {
        permissions.forEach {
            val oldPermissionState = permissionsStates[it]
            val newPermissionState = AndroidPermissionState.get(context, it)
            /**
             * Since [AndroidPermissionState.get] doesn't distinguish between
             * [AndroidPermissionState.DENIED] and [AndroidPermissionState.DENIED_DO_NOT_ASK] states,
             * we need to prevent [updatePermissionsStates] from over-writing
             * current [AndroidPermissionState.DENIED_DO_NOT_ASK] state.
             */
            if (oldPermissionState == AndroidPermissionState.DENIED_DO_NOT_ASK &&
                newPermissionState == AndroidPermissionState.DENIED
            ) {
                return
            }

            permissionsStates[it] = newPermissionState
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions

import android.content.Context
import com.splendo.kaluga.base.ApplicationHolder

actual data class PermissionContext(val context: Context)

actual val defaultPermissionContext get() = PermissionContext(ApplicationHolder.applicationContext)

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.permissions

import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import com.splendo.kaluga.logging.info

/**
 * An [AppCompatActivity] responsible for requesting a [Permission]
 */

class PermissionsActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        supportActionBar?.hide() // windowActionBar=false
        supportActionBar?.elevation = 0F // windowContentOverlay=@null

        val requestedPermissions = getRequestedPermissions(intent)

        if (requestedPermissions.isEmpty()) {
            finish()
        } else {
            ActivityCompat.requestPermissions(this, requestedPermissions, PERMISSIONS_REQUEST_CODE)
        }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        if (requestCode == PERMISSIONS_REQUEST_CODE) {
            permissions.forEach { permission ->
                val permissionState = AndroidPermissionState.get(this, permission)
                AndroidPermissionsManager.permissionsStates[permission] = permissionState
                info(TAG, "$permission was $permissionState")
            }
        }

        finish()
    }

    companion object {
        const val TAG = "PERMISSIONS"
        private const val PERMISSIONS_REQUEST_CODE = 32131
        private const val EXTRA_REQUESTED_PERMISSIONS = "EXTRA_REQUESTED_PERMISSIONS"

        fun intent(context: Context, vararg requestedPermissions: String): Intent {
            val intent = Intent(context, PermissionsActivity::class.java)

            intent.putExtra(EXTRA_REQUESTED_PERMISSIONS, requestedPermissions)

            PackageManager.PERMISSION_GRANTED

            return intent
        }

        fun getRequestedPermissions(intent: Intent): Array<String> {
            return intent.getStringArrayExtra(EXTRA_REQUESTED_PERMISSIONS) ?: emptyArray()
        }
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions

import co.touchlab.stately.collections.IsoMutableMap
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.transformLatest
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.KClassifier

/**
 * Permissions that can be requested by Kaluga
 * Each permission component should declare subclass of [Permission]
 */
abstract class Permission

/**
 *  Base type for all permissions builders. Each permission component implements a concrete permissions builder
 *  and declares helper methods for registering this builder in the [PermissionsBuilder].
 */
interface BasePermissionsBuilder

/**
 * Closure that takes permission and coroutine contexts and creates [PermissionStateRepo].
 * Each platform registers [PermissionStateRepoBuilder] in the register permission helper method.
 */
typealias PermissionStateRepoBuilder = (permission: Permission, coroutineContext: CoroutineContext) -> PermissionStateRepo<*>

expect class PermissionContext
expect val defaultPermissionContext: PermissionContext

/**
 * Builder for providing the proper [PermissionManager] for each [Permission]
 * @param context [PermissionContext] an additional parameter platform can pass to the [PermissionsBuilder]. [NSBundle] on iOS and [Contect] on Andoid.
 */
open class PermissionsBuilder(val context: PermissionContext = defaultPermissionContext) {

    private val builders = IsoMutableMap<KClassifier, BasePermissionsBuilder>()
    private val repoBuilders = IsoMutableMap<KClassifier, PermissionStateRepoBuilder>()

    fun <T : BasePermissionsBuilder> register(builder: T, permission: KClassifier): T {
        builders[permission] = builder
        return builder
    }

    operator fun get(permission: Permission): BasePermissionsBuilder =
        builders[permission::class] ?: throw Error("The Builder for $permission was not registered")

    fun registerPermissionStateRepoBuilder(permission: KClassifier, permissionStateRepoBuilder: PermissionStateRepoBuilder) {
        repoBuilders[permission] = permissionStateRepoBuilder
    }

    fun createPermissionStateRepo(permission: Permission, coroutineContext: CoroutineContext): PermissionStateRepo<*> =
        repoBuilders[permission::class]?.let { it(permission, coroutineContext) } ?: throw Error("Permission state repo factory was not registered for $permission")
}

/**
 * Manager to request the [PermissionStateRepo] of a given [Permission]
 * @param builder The [PermissionsBuilder] to build the [PermissionManager] associated with each [Permission]
 * @param coroutineContext The [CoroutineContext] to run permission checks from
 */
class Permissions(private val builder: PermissionsBuilder, private val coroutineContext: CoroutineContext = Dispatchers.Main) {

    private val permissionStateRepos: IsoMutableMap<Permission, PermissionStateRepo<*>> = IsoMutableMap()

    private fun <P : Permission> permissionStateRepo(permission: P) =
        (permissionStateRepos[permission] ?: builder.createPermissionStateRepo(permission, coroutineContext).also { permissionStateRepos[permission] = it }) as PermissionStateRepo<P>

    /**
     * Gets a [Flow] of [PermissionState] for a given [Permission]
     * @param permission The [Permission] for which the [PermissionState] flow should be provided
     * @return A [Flow] of [PermissionState] for the given [Permission]
     */
    operator fun <P : Permission> get(permission: P): Flow<PermissionState<P>> {
        return permissionStateRepo(permission)
    }

    /**
     * Gets a the of [PermissionManager] for a given [Permission]
     * @param permission The [Permission] for which the [PermissionManager] should be returned
     * @return The [PermissionManager] for the given [Permission]
     */
    fun <P : Permission> getManager(permission: P): PermissionManager<P> {
        return permissionStateRepo(permission).permissionManager
    }

    /**
     * Requests a [Permission]
     * @return `true` if the permission was granted, `false` otherwise.
     */
    suspend fun <P : Permission> request(p: P): Boolean {
        return get(p).request(getManager(p))
    }

    fun clean() {
        permissionStateRepos.values.forEach { it.cancel() }
        permissionStateRepos.clear()
    }
}

/**
 * Requests a [Permission] on a [Flow] of [PermissionState]
 * @return `true` if the permission was granted, `false` otherwise.
 */
suspend fun <P : Permission> Flow<PermissionState<out P>>.request(permissionManager: PermissionManager<out P>): Boolean {
    return this.transformLatest { state ->
        when (state) {
            is PermissionState.Allowed -> emit(true)
            is PermissionState.Denied.Requestable -> state.request(permissionManager)
            is PermissionState.Denied.Locked -> emit(false)
            is PermissionState.Unknown -> {}
        }
    }.first()
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.permissions

import com.splendo.kaluga.permissions.PermissionState.Allowed
import com.splendo.kaluga.permissions.PermissionState.Denied
import com.splendo.kaluga.permissions.PermissionState.Unknown
import kotlinx.coroutines.CoroutineScope

/**
 * Manager for maintaining the [PermissionState] of a given [Permission]
 * @param stateRepo The [PermissionStateRepo] managed by this manager.
 */
abstract class PermissionManager<P : Permission> constructor(private val stateRepo: PermissionStateRepo<P>) : CoroutineScope by stateRepo {

    /**
     * Requests the permission
     */
    abstract suspend fun requestPermission()

    /**
     * Determines the [PermissionState] at which to start.
     */
    abstract suspend fun initializeState(): PermissionState<P>

    /**
     * Starts monitoring for changes to the permission.
     * @param interval The interval in milliseconds between checking for changes to the permission state
     */
    abstract suspend fun startMonitoring(interval: Long)

    /**
     * Stops monitoring for chabges to the permission.
     */
    abstract suspend fun stopMonitoring()

    /**
     * Grants the permission
     * This will bring the [PermissionStateRepo] to [PermissionState.Allowed]
     */
    open fun grantPermission() {
        stateRepo.launchTakeAndChangeState { state ->
            when (state) {
                is Denied -> state.allow
                is Allowed, is Unknown -> state.remain()
            }
        }
    }

    /**
     * Revokes the permission
     * This will bring the [PermissionStateRepo] to [PermissionState.Denied]
     * @param locked `true` if the permission can no longer be requested after this. This corresponds to [PermissionState.Denied.Locked] when `true` and [PermissionState.Denied.Requestable] otherwise.
     */
    open fun revokePermission(locked: Boolean) {
        stateRepo.launchTakeAndChangeState { state ->
            when (state) {
                is Allowed -> suspend { state.deny(locked) }
                is Denied.Requestable -> {
                    if (locked) state.lock else state.remain()
                }
                is Denied.Locked -> {
                    if (locked) state.remain() else state.unlock
                }
                is Unknown -> state.remain()
            }
        }
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.permissions

import com.splendo.kaluga.base.flow.SpecialFlowValue
import com.splendo.kaluga.state.ColdStateFlowRepo
import com.splendo.kaluga.state.State
import kotlinx.coroutines.Dispatchers
import kotlin.coroutines.CoroutineContext

/**
 * State of a [Permission]
 */
sealed class PermissionState<P : Permission> : State() {

    class Unknown<P : Permission> : PermissionState<P>(), SpecialFlowValue.NotImportant

    // TODO: consider below states could be wrapped in a sealed Known, and Flows could expose PermissionState.Known where applicable

    /**
     * When in this state the [Permission] has been granted
     */
    class Allowed<P : Permission> : PermissionState<P>() {

        internal fun deny(locked: Boolean): Denied<P> {
            return if (locked) Denied.Locked() else Denied.Requestable()
        }
    }

    /**
     * When in this state the [Permission] is denied. If can either be requestable of blocked from request.
     */
    sealed class Denied<P : Permission> : PermissionState<P>() {

        internal val allow: suspend () -> Allowed<P> = {
            Allowed()
        }

        /**
         * When in this state the [Permission] is denied and cannot be requested
         */
        class Locked<P : Permission>() : Denied<P>() {

            internal val unlock: suspend () -> Requestable<P> = {
                Requestable()
            }
        }

        /**
         * When in this state the [Permission] is denied but can be requested. Use [request] to request the permission.
         */
        class Requestable<P : Permission> : Denied<P>() {

            suspend fun request(permissionManager: PermissionManager<out Permission>) {
                permissionManager.requestPermission()
            }

            internal val lock: suspend () -> Locked<P> = {
                Locked()
            }
        }
    }
}

/**
 * State machine for managing a given [Permission].
 * Since this is a [ColdStateFlowRepo], it will only monitor for changes to permissions while being observed.
 * @param monitoringInterval The interval in milliseconds between checking for a change in [PermissionState]
 */
abstract class PermissionStateRepo<P : Permission>(
    private val monitoringInterval: Long = defaultMonitoringInterval,
    coroutineContext: CoroutineContext = Dispatchers.Main.immediate
) : ColdStateFlowRepo<PermissionState<P>>(
    coroutineContext,
    initChangeStateWithRepo = { state, repo ->
        val pm = (repo as PermissionStateRepo<P>).permissionManager
        pm.startMonitoring(monitoringInterval)
        if (state == null || state is PermissionState.Unknown<P>)
            suspend { pm.initializeState() }
        else
            suspend { state }
    },
    deinitChangeStateWithRepo = { state, repo ->
        (repo as PermissionStateRepo<P>).permissionManager.stopMonitoring() // TODO: could also be replaced by a state
        null
    },
    firstState = { PermissionState.Unknown() }
) {

    companion object {
        const val defaultMonitoringInterval: Long = 1000
    }

    // TODO move to constructor, so no explicit cast is needed in init block
    abstract val permissionManager: PermissionManager<P>
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.permissions

import com.splendo.kaluga.logging.debug
import com.splendo.kaluga.logging.error
import platform.Foundation.NSBundle

/**
 * Convenience class for checking permissions.
 */
class IOSPermissionsHelper {

    /**
     * Type of AuthorizationStatus that can be given by a permission.
     */
    enum class AuthorizationStatus {
        NotDetermined,
        Restricted,
        Denied,
        Authorized
    }

    companion object {

        private const val TAG = "Permissions"

        /**
         * Checks whether a set of declarations have been provided in the PList of a [NSBundle]
         * @param bundle The [NSBundle] to check
         * @param requiredDeclarationName List of declarations that should be present in the PList
         * @return The list of declarations not present in the PList. If empty all declarations where provided.
         */
        fun missingDeclarationsInPList(
            bundle: NSBundle,
            vararg requiredDeclarationName: String
        ) = requiredDeclarationName.mapNotNull { declaration ->
            try {
                if (bundle.objectForInfoDictionaryKey(declaration) == null) {
                    error(TAG, "$declaration was not declared")
                    declaration
                } else {
                    debug(TAG, "$declaration was declared")
                    null
                }
            } catch (error: Exception) {
                error(TAG, error)
                null
            }
        }

        /**
         * Maps an [AuthorizationStatus] to a [PermissionState]
         * @param authorizationStatus The [AuthorizationStatus] to map
         * @param permissionManager The [PermissionManager] associated with the [PermissionState]
         */
        fun <P : Permission> getPermissionState(authorizationStatus: AuthorizationStatus): PermissionState<P> {
            return when (authorizationStatus) {
                AuthorizationStatus.NotDetermined -> PermissionState.Denied.Requestable()
                AuthorizationStatus.Authorized -> PermissionState.Allowed()
                AuthorizationStatus.Denied, AuthorizationStatus.Restricted -> PermissionState.Denied.Locked()
            }
        }

        /**
         * Updates a [PermissionManager] with the [PermissionState] associated with a given [AuthorizationStatus]
         * @param authorizationStatus The [AuthorizationStatus] to update to
         * @param permissionManager The [PermissionManager] to update to the proper state.
         */
        fun <P : Permission> handleAuthorizationStatus(authorizationStatus: AuthorizationStatus, permissionManager: PermissionManager<P>) {
            when (authorizationStatus) {
                AuthorizationStatus.NotDetermined -> permissionManager.revokePermission(false)
                AuthorizationStatus.Authorized -> permissionManager.grantPermission()
                AuthorizationStatus.Denied, AuthorizationStatus.Restricted -> permissionManager.revokePermission(true)
            }
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions

import platform.Foundation.NSBundle

actual typealias PermissionContext = NSBundle

actual val defaultPermissionContext get() = NSBundle.mainBundle

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions

import co.touchlab.stately.concurrency.AtomicBoolean
import co.touchlab.stately.concurrency.AtomicReference
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

/**
 * Convenience class for scheduling checks to changes in permission state.
 * @param permissionManager The [PermissionManager] to monitor changes for.
 * @param authorizationStatus Method for requesting a the current [IOSPermissionsHelper.AuthorizationStatus] for the permission associated with the [PermissionManager]
 * @param coroutineScope The [CoroutineScope] on which to run the checks.
 */
class PermissionRefreshScheduler<P : Permission>(
    private val permissionManager: PermissionManager<P>,
    private val authorizationStatus: suspend () -> IOSPermissionsHelper.AuthorizationStatus,
    coroutineScope: CoroutineScope = permissionManager
) : CoroutineScope by coroutineScope {

    private sealed class TimerJobState {
        object TimerNotRunning : TimerJobState() {
            fun startTimer(interval: Long, coroutineScope: CoroutineScope, block: suspend () -> Unit): TimerJobState = TimerRunning(interval, block, coroutineScope)
        }

        class TimerRunning(val interval: Long, val block: suspend () -> Unit, coroutineScope: CoroutineScope) : TimerJobState() {
            private val timerLoop = coroutineScope.launch {
                while (isActive) {
                    delay(interval)
                    block()
                }
            }

            fun stopTimer() = TimerNotRunning.also { timerLoop.cancel() }
        }
    }

    private val lastPermission: AtomicReference<IOSPermissionsHelper.AuthorizationStatus?> = AtomicReference(null)
    val isWaiting = AtomicBoolean(false)
    private val timerState: AtomicReference<TimerJobState> = AtomicReference(TimerJobState.TimerNotRunning)
    private val timerLock = Mutex()

    /**
     * Starts monitoring for changes to the permission
     * @param interval The interval in milliseconds between checking for changes to the permission.
     */
    suspend fun startMonitoring(interval: Long) {
        updateLastPermission()
        launchTimerJob(interval)
    }

    private suspend fun launchTimerJob(interval: Long) {
        timerLock.withLock {
            val timerJobState = timerState.get()
            if (timerJobState is TimerJobState.TimerNotRunning) {
                this.timerState.set(
                    timerJobState.startTimer(interval, this) {
                        val status = authorizationStatus()
                        if (!isWaiting.value && lastPermission.get() != status) {
                            updateLastPermission()
                            IOSPermissionsHelper.handleAuthorizationStatus(
                                status,
                                permissionManager
                            )
                        }
                    }
                )
            }
        }
    }

    /**
     * Stops monitoring for changes to the permission.
     */
    suspend fun stopMonitoring() {
        timerLock.withLock {
            val timerJobState = timerState.get()
            if (timerJobState is TimerJobState.TimerRunning) {
                this.timerState.set(timerJobState.stopTimer())
            }
        }
    }

    private suspend fun updateLastPermission() {
        lastPermission.set(authorizationStatus())
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions

actual class PermissionContext

actual val defaultPermissionContext = PermissionContext()

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions

actual class PermissionContext

actual val defaultPermissionContext = PermissionContext()

package com.splendo.kaluga.base

import android.app.Application
import android.content.Context

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.

*/

class ApplicationHolder {
    companion object {
        var application: Application? = null
            set(application) {
                check(field == null) { "Application object can only be set once." }
                field = application
            }
        val applicationContext: Context
            get() {
                val application = this.application
                checkNotNull(application) {
                    "You've used ApplicationHolder.applicationContext without setting the Application on this holder (you should do this from Application.onCreate() or in your test)"
                }

                val context = application.applicationContext
                checkNotNull(context) {
                    "ApplicationContext is null, this should not happen during runtime if you set a real Application instance on this holder. For testing make sure you set an Application with a real or mock ApplicationContext available."
                }

                return context
            }
    }
}

package com.splendo.kaluga.base

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.

*/

@Deprecated("Use Dispatchers.Main")
actual val MainQueueDispatcher: CoroutineDispatcher = Dispatchers.Main

/*
 * Copyright 2022 Splendo Consulting B.V. The Netherlands
 *
 *     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.splendo.kaluga.base

import android.os.Looper
import kotlinx.coroutines.Dispatchers

actual val isOnMainThread: Boolean get() {
    val mainThread = Looper.getMainLooper()?.thread ?: run {
        // Fallback when no MainLooper is present. This should only occur in tests
        runBlocking(Dispatchers.Main) { Thread.currentThread() }
    }
    return mainThread == Thread.currentThread()
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.base

import kotlinx.coroutines.CoroutineScope
import kotlin.coroutines.CoroutineContext

actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T = kotlinx.coroutines.runBlocking(context, block)

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.base

import android.os.Handler
import android.os.Looper

var mainHandler = Handler(Looper.getMainLooper())

actual fun runOnMain(block: () -> Unit) {
    mainHandler.post(block)
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base

import co.touchlab.stately.concurrency.AtomicReference
import kotlin.reflect.KProperty

/**
 * Multiplatform [AtomicReference] property wrapper.
 *
 * Example:
 * ```
 * val atomicJobRef: Job? by AtomicReferenceDelegate()
 * atomicJobRef = Job()
 * atomicJobRef = null
 * ```
 */
class AtomicReferenceDelegate<T>(initialValue: T? = null) {
    private val reference = AtomicReference(initialValue)
    operator fun getValue(thisRef: Any?, property: KProperty<*>) = reference.get()
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) = reference.set(value)
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.base

import kotlinx.coroutines.CoroutineDispatcher

@Deprecated("Use Dispatchers.Main")
expect val MainQueueDispatcher: CoroutineDispatcher

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope

@Deprecated("Use MainScope", ReplaceWith("MainScope()", "kotlinx.coroutines.MainScope"))
fun MultiplatformMainScope(): CoroutineScope {
    return MainScope()
}

/*
 * Copyright 2022 Splendo Consulting B.V. The Netherlands
 *
 *     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.splendo.kaluga.base

expect val isOnMainThread: Boolean

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.base

import kotlinx.coroutines.CoroutineScope
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

expect fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.base

expect fun runOnMain(block: () -> Unit)

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base

import co.touchlab.stately.concurrency.AtomicBoolean
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlin.native.concurrent.ThreadLocal
import kotlin.native.internal.GC

@SharedImmutable
private val isCollecting: AtomicBoolean = AtomicBoolean(false)

@ThreadLocal
object GCScheduler {

    /**
     * Schedules the Garbage Collector only if no other garbage collection is active.
     * This is useful in case calling the Garbage collector could trigger another call to [GC.collect].
     */
    fun schedule() {
        MainScope().launch { collectIfNotCollecting() }
    }

    /**
     * Runs the Garbage Collector only if no other garbage collection is active.
     * This is useful in case calling the Garbage collector could trigger another call to [GC.collect].
     */
    fun collectIfNotCollecting() {
        if (isCollecting.compareAndSet(expected = false, new = true)) {
            GC.collect()
            isCollecting.value = false
        }
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base

import platform.UIKit.UIDevice

/**
 * Wrapper for the iOS OS Version
 */
data class IOSVersion(val major: Int, val minor: Int = 0, val patch: Int = 0) : Comparable<IOSVersion> {

    companion object {
        /**
         * Gets the [IOSVersion] for the current device
         */
        val systemVersion: IOSVersion
            get() {
                val versions = UIDevice.currentDevice.systemVersion.split(".").map { it.toIntOrNull() ?: 0 }
                return IOSVersion(versions.getOrNull(0) ?: 0, versions.getOrNull(1) ?: 0, versions.getOrNull(2) ?: 0)
            }
    }

    override fun compareTo(other: IOSVersion): Int {
        return when {
            this.major > other.major -> 1
            this.major == other.major -> {
                when {
                    this.minor > other.minor -> 1
                    this.minor == other.minor -> this.patch.compareTo(other.patch)
                    else -> -1
                }
            }
            else -> -1
        }
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.base

import kotlinx.cinterop.staticCFunction
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import platform.Foundation.NSThread
import platform.darwin.dispatch_get_main_queue
import platform.darwin.dispatch_sync_f
import kotlin.native.concurrent.Continuation0
import kotlin.native.concurrent.Continuation1
import kotlin.native.concurrent.Continuation2
import kotlin.native.concurrent.callContinuation0
import kotlin.native.concurrent.callContinuation1
import kotlin.native.concurrent.callContinuation2

@Deprecated("Use Dispatchers.Main")
actual val MainQueueDispatcher: CoroutineDispatcher = Dispatchers.Main

inline fun mainContinuation(singleShot: Boolean = true, noinline block: () -> Unit) = Continuation0(
    block,
    staticCFunction { invokerArg ->
        if (NSThread.isMainThread()) {
            invokerArg!!.callContinuation0()
        } else {
            dispatch_sync_f(
                dispatch_get_main_queue(),
                invokerArg,
                staticCFunction { args ->
                    args!!.callContinuation0()
                }
            )
        }
    },
    singleShot
)

inline fun <T1> mainContinuation(singleShot: Boolean = true, noinline block: (T1) -> Unit) = Continuation1(
    block,
    staticCFunction { invokerArg ->
        if (NSThread.isMainThread()) {
            invokerArg!!.callContinuation1<T1>()
        } else {
            dispatch_sync_f(
                dispatch_get_main_queue(),
                invokerArg,
                staticCFunction { args ->
                    args!!.callContinuation1<T1>()
                }
            )
        }
    },
    singleShot
)

inline fun <T1, T2> mainContinuation(singleShot: Boolean = true, noinline block: (T1, T2) -> Unit) = Continuation2(
    block,
    staticCFunction { invokerArg ->
        if (NSThread.isMainThread()) {
            invokerArg!!.callContinuation2<T1, T2>()
        } else {
            dispatch_sync_f(
                dispatch_get_main_queue(),
                invokerArg,
                staticCFunction { args ->
                    args!!.callContinuation2<T1, T2>()
                }
            )
        }
    },
    singleShot
)

/*
 * Copyright 2022 Splendo Consulting B.V. The Netherlands
 *
 *     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.splendo.kaluga.base

import platform.Foundation.NSThread

actual val isOnMainThread: Boolean get() = NSThread.isMainThread

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.base

import kotlinx.coroutines.CoroutineScope
import kotlin.coroutines.CoroutineContext

actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T = kotlinx.coroutines.runBlocking(context, block)

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.base

import platform.darwin.dispatch_async
import platform.darwin.dispatch_get_main_queue

actual fun runOnMain(block: () -> Unit) {
    dispatch_async(dispatch_get_main_queue(), block)
}

package com.splendo.kaluga.base

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.

*/

actual val MainQueueDispatcher: CoroutineDispatcher = Dispatchers.Main

/*
 * Copyright 2022 Splendo Consulting B.V. The Netherlands
 *
 *     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.splendo.kaluga.base

actual val isOnMainThread: Boolean get() = true

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.base

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext

actual fun <T> runBlocking(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): dynamic {
    // this does not wait for the result unfortunately
    return GlobalScope.launch { block(this) }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.base

actual fun runOnMain(block: () -> Unit) {
    block()
}

package com.splendo.kaluga.base

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.

*/

actual val MainQueueDispatcher: CoroutineDispatcher = Dispatchers.Main

/*
 * Copyright 2022 Splendo Consulting B.V. The Netherlands
 *
 *     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.splendo.kaluga.base

import javax.swing.SwingUtilities

actual val isOnMainThread: Boolean get() = SwingUtilities.isEventDispatchThread()

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.base

import kotlinx.coroutines.CoroutineScope
import kotlin.coroutines.CoroutineContext

actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T = kotlinx.coroutines.runBlocking(context, block)

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.base

import javax.swing.SwingUtilities

actual fun runOnMain(block: () -> Unit) {
    SwingUtilities.invokeLater(block)
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.beacons

import com.splendo.kaluga.base.utils.Date
import com.splendo.kaluga.bluetooth.device.Identifier

typealias BeaconID = Eddystone.UID
typealias TxPower = Int
typealias RSSI = Int

data class BeaconInfo(
    var identifier: Identifier,
    var beaconID: BeaconID,
    var txPower: TxPower,
    var RSSI: RSSI,
    var lastSeen: Date
)

@Deprecated(
    message = "Replaced with beaconId.asString()",
    replaceWith = ReplaceWith(expression = "beaconID.asString()")
)
fun BeaconInfo.fullID() = this.beaconID.namespace + this.beaconID.instance
fun BeaconInfo.seenMs() = Date.now().millisecondSinceEpoch - lastSeen.millisecondSinceEpoch

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.beacons

import co.touchlab.stately.collections.IsoMutableMap
import com.splendo.kaluga.base.AtomicReferenceDelegate
import com.splendo.kaluga.base.utils.Date
import com.splendo.kaluga.bluetooth.BluetoothService
import com.splendo.kaluga.bluetooth.device.Device
import com.splendo.kaluga.bluetooth.device.Identifier
import com.splendo.kaluga.logging.debug
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

private typealias BeaconJob = Pair<BeaconInfo, Job>
private typealias BeaconsMap = IsoMutableMap<BeaconID, BeaconJob>

class Beacons(
    private val bluetooth: BluetoothService,
    private val timeoutMs: Long = 10_000,
) {

    private companion object { const val TAG = "Beacons" }

    private val cache = BeaconsMap()
    private val cacheJob = Job()
    private val coroutineScope = CoroutineScope(Dispatchers.Default + cacheJob)
    private var monitoringJob: Job? by AtomicReferenceDelegate()

    private val _beacons = MutableStateFlow(emptySet<BeaconInfo>())
    val beacons: StateFlow<Set<BeaconInfo>>
        get() = _beacons.asStateFlow()

    fun startMonitoring(coroutineScope: CoroutineScope) {
        debug(TAG, "Start monitoring")
        _beacons.value = emptySet()
        bluetooth.startScanning()
        monitoringJob = coroutineScope.launch {
            bluetooth.devices().collect { list ->
                debug(TAG, "Total Bluetooth devices discovered: ${list.size}")
                updateBeacons(list.mapNotNull { createBeaconWith(it) })
            }
        }
    }

    fun stopMonitoring() {
        debug(TAG, "Stop monitoring")
        bluetooth.stopScanning()
        monitoringJob?.cancel()
        monitoringJob = null
        _beacons.value = emptySet()
    }

    suspend fun isMonitoring() = bluetooth.isScanning()

    fun isAnyInRange(beaconIds: List<String>) = beacons.map { list ->
        list.any { beaconIds.containsLowerCased(it.beaconID.asString()) }
    }

    private suspend fun updateBeacons(discovered: List<BeaconInfo>) {
        debug(TAG, "Total Beacons discovered: ${discovered.size}")
        discovered.forEach { beacon ->
            if (cache.containsKey(beacon.beaconID)) {
                debug(TAG, "[Found] $beacon")
                cache.remove(beacon.beaconID)?.second?.cancel()
                debug(TAG, "[Removed] $beacon")
            } else {
                debug(TAG, "[New] $beacon")
            }
            cache[beacon.beaconID] = beacon to coroutineScope.launch {
                debug(TAG, "[Added] $beacon")
                delay(timeoutMs)
                debug(TAG, "[Lost] $beacon")
                cache.remove(beacon.beaconID)
                updateList()
            }
        }
        updateList()
    }

    private fun updateList() = _beacons.update {
        cache.values.map(BeaconJob::first).toSet()
    }

    private fun List<String>.containsLowerCased(element: String): Boolean =
        this.map(String::lowercase).contains(element.lowercase())

    private suspend fun createBeaconWith(device: Device): BeaconInfo? {
        val serviceData = device.map { it.advertisementData.serviceData }.firstOrNull() ?: return null
        val data = serviceData[Eddystone.SERVICE_UUID] ?: return null
        val frame = Eddystone.unpack(data) ?: return null
        val rssi = device.map { it.rssi }.firstOrNull() ?: 0
        val lastSeen = device.map { it.updatedAt }.firstOrNull() ?: Date.now()
        return BeaconInfo(device.identifier, frame.uid, frame.txPower, rssi, lastSeen)
    }
}

operator fun Flow<Set<BeaconInfo>>.get(identifier: Identifier) = map { beacons ->
    beacons.firstOrNull { it.identifier == identifier }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.beacons

import com.splendo.kaluga.base.utils.toHexString
import com.splendo.kaluga.bluetooth.uuidFrom

private fun List<Byte>.toHexString() = toByteArray().toHexString()

class Eddystone {

    data class UID(val namespace: String, val instance: String) {
        fun asString() = namespace + instance
    }

    data class Frame(val txPower: TxPower, val uid: UID)

    companion object {

        val SERVICE_UUID = uuidFrom("0000FEAA-0000-1000-8000-00805F9B34FB")

        private const val VALID_FRAME_SIZE = 18
        private const val UID_FRAME_TYPE = 0x00

        private fun ByteArray.isValidUIDFrame() =
            this.size == VALID_FRAME_SIZE && this[0] == UID_FRAME_TYPE.toByte()

        fun unpack(data: ByteArray) = if (data.isValidUIDFrame()) {
            val txPower = data[1]
            val namespace = data.slice(2..11)
            val instance = data.slice(12..17)
            Frame(
                txPower.toInt(),
                UID(namespace.toHexString(), instance.toHexString())
            )
        } else null
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.bluetooth

import android.Manifest
import android.bluetooth.BluetoothAdapter
import android.content.Context
import com.splendo.kaluga.permissions.AndroidPermissionsManager
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class BluetoothPermissionManager(
    context: Context,
    bluetoothAdapter: BluetoothAdapter?,
    stateRepo: BluetoothPermissionStateRepo
) : PermissionManager<BluetoothPermission>(stateRepo) {

    private val permissionsManager = AndroidPermissionsManager(context, this, arrayOf(Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_FINE_LOCATION))
    private val supported: Boolean = bluetoothAdapter != null

    override suspend fun requestPermission() {
        if (supported)
            permissionsManager.requestPermissions()
    }

    override suspend fun initializeState(): PermissionState<BluetoothPermission> {
        return when {
            !supported -> PermissionState.Denied.Locked()
            permissionsManager.hasPermissions -> PermissionState.Allowed()
            else -> PermissionState.Denied.Requestable()
        }
    }

    override suspend fun startMonitoring(interval: Long) {
        if (supported)
            permissionsManager.startMonitoring(interval)
    }

    override suspend fun stopMonitoring() {
        if (supported)
            permissionsManager.stopMonitoring()
    }
}

actual class BluetoothPermissionManagerBuilder(
    private val context: PermissionContext,
    private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
) : BaseBluetoothPermissionManagerBuilder {

    actual constructor(context: PermissionContext) : this(context, BluetoothAdapter.getDefaultAdapter())

    override fun create(repo: BluetoothPermissionStateRepo): PermissionManager<BluetoothPermission> {
        return BluetoothPermissionManager(context.context, bluetoothAdapter, repo)
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.permissions.bluetooth

import com.splendo.kaluga.permissions.BasePermissionsBuilder
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionStateRepo
import com.splendo.kaluga.permissions.defaultPermissionContext
import kotlin.coroutines.CoroutineContext

/**
 * A [PermissionManager] for managing [BluetoothPermission]
 */
expect class BluetoothPermissionManager : PermissionManager<BluetoothPermission>

interface BaseBluetoothPermissionManagerBuilder : BasePermissionsBuilder {
    /**
     * Creates a [BluetoothPermissionManager]
     * @param repo The [BluetoothPermissionStateRepo] associated with the [BluetoothPermission]
     */
    fun create(repo: BluetoothPermissionStateRepo): PermissionManager<BluetoothPermission>
}

/**
 * A builder for creating a [BluetoothPermissionManager]
 */
expect class BluetoothPermissionManagerBuilder(context: PermissionContext = defaultPermissionContext) : BaseBluetoothPermissionManagerBuilder

/**
 * A [PermissionStateRepo] for [BluetoothPermission]
 * @param builder The [BluetoothPermissionManagerBuilder] for creating the [BluetoothPermissionManager] associated with the permission
 * @param coroutineContext The [CoroutineContext] to run the state machine on.
 */
class DefaultBluetoothPermissionStateRepo(
    builder: BaseBluetoothPermissionManagerBuilder,
    coroutineContext: CoroutineContext
) : BluetoothPermissionStateRepo(coroutineContext = coroutineContext) {
    override val permissionManager: PermissionManager<BluetoothPermission> =
        builder.create(this)
}

abstract class BluetoothPermissionStateRepo(coroutineContext: CoroutineContext) : PermissionStateRepo<BluetoothPermission>(coroutineContext = coroutineContext)

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.bluetooth

import com.splendo.kaluga.permissions.Permission
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.defaultPermissionContext

/**
 * Permission to access the Bluetooth scanner
 */
object BluetoothPermission : Permission()

fun PermissionsBuilder.registerBluetoothPermission() =
    registerBluetoothBuilder(context).also { builder ->
        registerPermissionStateRepoBuilder(BluetoothPermission::class) { _, coroutineContext ->
            DefaultBluetoothPermissionStateRepo(builder as BaseBluetoothPermissionManagerBuilder, coroutineContext)
        }
    }

internal fun PermissionsBuilder.registerBluetoothBuilder(context: PermissionContext = defaultPermissionContext): BluetoothPermissionManagerBuilder = register(
    builder = BluetoothPermissionManagerBuilder(context),
    permission = BluetoothPermission::class
)

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.permissions.bluetooth

import com.splendo.kaluga.base.IOSVersion
import com.splendo.kaluga.logging.error
import com.splendo.kaluga.permissions.IOSPermissionsHelper
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState
import platform.CoreBluetooth.CBCentralManager
import platform.CoreBluetooth.CBCentralManagerDelegateProtocol
import platform.CoreBluetooth.CBCentralManagerOptionShowPowerAlertKey
import platform.CoreBluetooth.CBManagerAuthorization
import platform.CoreBluetooth.CBManagerAuthorizationAllowedAlways
import platform.CoreBluetooth.CBManagerAuthorizationDenied
import platform.CoreBluetooth.CBManagerAuthorizationNotDetermined
import platform.CoreBluetooth.CBManagerAuthorizationRestricted
import platform.CoreBluetooth.CBPeripheralManager
import platform.CoreBluetooth.CBPeripheralManagerAuthorizationStatus
import platform.CoreBluetooth.CBPeripheralManagerAuthorizationStatusAuthorized
import platform.CoreBluetooth.CBPeripheralManagerAuthorizationStatusDenied
import platform.CoreBluetooth.CBPeripheralManagerAuthorizationStatusNotDetermined
import platform.CoreBluetooth.CBPeripheralManagerAuthorizationStatusRestricted
import platform.Foundation.NSBundle
import platform.darwin.NSObject
import platform.darwin.dispatch_get_main_queue

const val NSBluetoothAlwaysUsageDescription = "NSBluetoothAlwaysUsageDescription"
const val NSBluetoothPeripheralUsageDescription = "NSBluetoothPeripheralUsageDescription"

actual class BluetoothPermissionManager(
    private val bundle: NSBundle,
    stateRepo: BluetoothPermissionStateRepo
) : PermissionManager<BluetoothPermission>(stateRepo) {

    private val centralManager = lazy {
        val options = mapOf<Any?, Any>(CBCentralManagerOptionShowPowerAlertKey to false)
        CBCentralManager(null, dispatch_get_main_queue(), options)
    }

    private val delegate = object : NSObject(), CBCentralManagerDelegateProtocol {

        override fun centralManagerDidUpdateState(central: CBCentralManager) {
            IOSPermissionsHelper.handleAuthorizationStatus(checkAuthorization(), this@BluetoothPermissionManager)
        }
    }

    override suspend fun requestPermission() {
        if (IOSPermissionsHelper.missingDeclarationsInPList(bundle, NSBluetoothAlwaysUsageDescription, NSBluetoothPeripheralUsageDescription).isEmpty()) {
            if (!centralManager.isInitialized()) {
                centralManager.value
            }
        } else {
            revokePermission(true)
        }
    }

    override suspend fun initializeState(): PermissionState<BluetoothPermission> {
        return IOSPermissionsHelper.getPermissionState(checkAuthorization())
    }

    override suspend fun startMonitoring(interval: Long) {
        centralManager.value.delegate = delegate
    }

    override suspend fun stopMonitoring() {
        centralManager.value.delegate = null
    }

    private fun checkAuthorization(): IOSPermissionsHelper.AuthorizationStatus {
        val version = IOSVersion.systemVersion
        return when {
            version >= IOSVersion(13) -> CBCentralManager().authorization.toAuthorizationStatus()
            else -> CBPeripheralManager.authorizationStatus().toPeripheralAuthorizationStatus()
        }
    }
}

actual class BluetoothPermissionManagerBuilder actual constructor(
    private val context: PermissionContext
) : BaseBluetoothPermissionManagerBuilder {

    override fun create(repo: BluetoothPermissionStateRepo): PermissionManager<BluetoothPermission> {
        return BluetoothPermissionManager(context, repo)
    }
}

private fun CBPeripheralManagerAuthorizationStatus.toPeripheralAuthorizationStatus(): IOSPermissionsHelper.AuthorizationStatus {
    return when (this) {
        CBPeripheralManagerAuthorizationStatusAuthorized -> IOSPermissionsHelper.AuthorizationStatus.Authorized
        CBPeripheralManagerAuthorizationStatusDenied -> IOSPermissionsHelper.AuthorizationStatus.Denied
        CBPeripheralManagerAuthorizationStatusRestricted -> IOSPermissionsHelper.AuthorizationStatus.Restricted
        CBPeripheralManagerAuthorizationStatusNotDetermined -> IOSPermissionsHelper.AuthorizationStatus.NotDetermined
        else -> {
            error(
                "BluetoothPermissionManager",
                "Unknown CBPeripheralManagerAuthorizationStatus status={$this}"
            )
            IOSPermissionsHelper.AuthorizationStatus.NotDetermined
        }
    }
}

private fun CBManagerAuthorization.toAuthorizationStatus(): IOSPermissionsHelper.AuthorizationStatus {
    return when (this) {
        CBManagerAuthorizationAllowedAlways -> IOSPermissionsHelper.AuthorizationStatus.Authorized
        CBManagerAuthorizationDenied -> IOSPermissionsHelper.AuthorizationStatus.Denied
        CBManagerAuthorizationRestricted -> IOSPermissionsHelper.AuthorizationStatus.Restricted
        CBManagerAuthorizationNotDetermined -> IOSPermissionsHelper.AuthorizationStatus.NotDetermined
        else -> {
            error(
                "BluetoothPermissionManager",
                "Unknown CBManagerAuthorization status={$this}"
            )
            IOSPermissionsHelper.AuthorizationStatus.NotDetermined
        }
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.bluetooth

import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class BluetoothPermissionManager(repo: BluetoothPermissionStateRepo) : PermissionManager<BluetoothPermission>(repo) {

    override suspend fun requestPermission() {
        TODO("not implemented")
    }

    override suspend fun initializeState(): PermissionState<BluetoothPermission> {
        TODO("not implemented")
    }

    override suspend fun startMonitoring(interval: Long) {
        TODO("not implemented")
    }

    override suspend fun stopMonitoring() {
        TODO("not implemented")
    }
}

actual class BluetoothPermissionManagerBuilder actual constructor(context: PermissionContext) : BaseBluetoothPermissionManagerBuilder {

    override fun create(repo: BluetoothPermissionStateRepo): PermissionManager<BluetoothPermission> {
        return BluetoothPermissionManager(repo)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.bluetooth

import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class BluetoothPermissionManager(repo: BluetoothPermissionStateRepo) : PermissionManager<BluetoothPermission>(repo) {

    override suspend fun requestPermission() {
        TODO("not implemented")
    }

    override suspend fun initializeState(): PermissionState<BluetoothPermission> {
        TODO("not implemented")
    }

    override suspend fun startMonitoring(interval: Long) {
        TODO("not implemented")
    }

    override suspend fun stopMonitoring() {
        TODO("not implemented")
    }
}

actual class BluetoothPermissionManagerBuilder actual constructor(context: PermissionContext) : BaseBluetoothPermissionManagerBuilder {

    override fun create(repo: BluetoothPermissionStateRepo): PermissionManager<BluetoothPermission> {
        return BluetoothPermissionManager(repo)
    }
}

package com.splendo.kaluga.bluetooth

import android.content.Context
import com.splendo.kaluga.base.ApplicationHolder
import com.splendo.kaluga.bluetooth.device.ConnectionSettings
import com.splendo.kaluga.bluetooth.scanner.Scanner
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermission
import com.splendo.kaluga.permissions.location.registerLocationPermission
import kotlinx.coroutines.CoroutineScope

actual class BluetoothBuilder(
    private val applicationContext: Context = ApplicationHolder.applicationContext,
    private val permissions: Permissions = Permissions(
        PermissionsBuilder(PermissionContext(applicationContext)).apply {
            registerBluetoothPermission()
            registerLocationPermission()
        }
    ),
    private val scannerBuilder: Scanner.Builder = Scanner.Builder(applicationContext = applicationContext)
) : Bluetooth.Builder {

    override fun create(connectionSettings: ConnectionSettings, autoRequestPermission: Boolean, autoEnableBluetooth: Boolean, coroutineScope: CoroutineScope): Bluetooth {
        return Bluetooth(permissions, connectionSettings, autoRequestPermission, autoEnableBluetooth, scannerBuilder, coroutineScope)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import android.bluetooth.BluetoothAdapter
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import com.splendo.kaluga.base.ApplicationHolder
import com.splendo.kaluga.base.DefaultServiceMonitor
import com.splendo.kaluga.base.ServiceMonitor

actual interface BluetoothMonitor : ServiceMonitor {

    actual class Builder(
        private val context: Context = ApplicationHolder.applicationContext,
        private val adapter: BluetoothAdapter
    ) {
        actual fun create(): BluetoothMonitor {
            return DefaultBluetoothMonitor(
                applicationContext = context,
                bluetoothAdapter = adapter
            )
        }
    }
}

class DefaultBluetoothMonitor internal constructor(
    private val applicationContext: Context,
    private val bluetoothAdapter: BluetoothAdapter
) : DefaultServiceMonitor(), BluetoothMonitor {

    private val availabilityBroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (intent?.action == BluetoothAdapter.ACTION_STATE_CHANGED) {
                updateState()
            }
        }
    }

    @Suppress("SENSELESS_COMPARISON") // Can still be null on initialization
    override val isServiceEnabled: Boolean
        get() = if (bluetoothAdapter == null) {
            false
        } else {
            bluetoothAdapter.isEnabled
        }

    override fun startMonitoring() {
        super.startMonitoring()
        applicationContext.registerReceiver(
            availabilityBroadcastReceiver,
            IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
        )
    }

    override fun stopMonitoring() {
        super.stopMonitoring()
        applicationContext.unregisterReceiver(availabilityBroadcastReceiver)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import android.bluetooth.BluetoothGattCharacteristic

actual interface CharacteristicWrapper {

    actual val uuid: java.util.UUID
    actual val value: ByteArray?
    fun updateValue(value: ByteArray?)

    val service: ServiceWrapper
    actual val descriptors: List<DescriptorWrapper>
    val permissions: Int
    val properties: Int
    var writeType: Int

    fun setValue(newValue: String): Boolean
    fun setValue(newValue: ByteArray?): Boolean
    fun setValue(mantissa: Int, exponent: Int, formatType: Int, offset: Int): Boolean
    fun getDescriptor(uuid: java.util.UUID): DescriptorWrapper?

    fun floatValue(formatType: Int, offset: Int): Float
    fun intValue(formatType: Int, offset: Int): Int
}

fun CharacteristicWrapper.containsAnyOf(vararg property: Int) =
    if (property.count() > 0)
        properties and property.reduce { acc, i -> acc.or(i) } != 0
    else
        false

class DefaultCharacteristicWrapper(private val gattCharacteristic: BluetoothGattCharacteristic) : CharacteristicWrapper {

    override val uuid: java.util.UUID
        get() { return gattCharacteristic.uuid }
    override val value: ByteArray?
        get() {
            return gattCharacteristic.value
        }

    override fun updateValue(value: ByteArray?) {
        gattCharacteristic.value = value
    }

    override val service: ServiceWrapper
        get() = DefaultGattServiceWrapper(gattCharacteristic.service)
    override val descriptors: List<DescriptorWrapper>
        get() { return gattCharacteristic.descriptors.map { DefaultDescriptorWrapper(it) } }
    override val permissions: Int
        get() { return gattCharacteristic.permissions }
    override val properties: Int
        get() { return gattCharacteristic.properties }
    override var writeType: Int
        get() { return gattCharacteristic.writeType }
        set(value) { gattCharacteristic.writeType = value }

    override fun setValue(newValue: String): Boolean {
        return gattCharacteristic.setValue(newValue)
    }

    override fun setValue(newValue: ByteArray?): Boolean {
        return gattCharacteristic.setValue(newValue)
    }

    override fun setValue(mantissa: Int, exponent: Int, formatType: Int, offset: Int): Boolean {
        return gattCharacteristic.setValue(mantissa, exponent, formatType, offset)
    }

    override fun getDescriptor(uuid: java.util.UUID): DescriptorWrapper? {
        return gattCharacteristic.getDescriptor(uuid)?.let { DefaultDescriptorWrapper(it) }
    }

    override fun floatValue(formatType: Int, offset: Int): Float {
        return gattCharacteristic.getFloatValue(formatType, offset)
    }

    override fun intValue(formatType: Int, offset: Int): Int {
        return gattCharacteristic.getIntValue(formatType, offset)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import android.bluetooth.BluetoothGattDescriptor

actual interface DescriptorWrapper {
    actual val uuid: java.util.UUID
    actual val value: ByteArray?
    fun updateValue(value: ByteArray?)

    val permissions: Int
    val characteristic: CharacteristicWrapper

    fun setValue(newValue: ByteArray?): Boolean
}

class DefaultDescriptorWrapper(private val gattDescriptor: BluetoothGattDescriptor) : DescriptorWrapper {

    override val uuid: java.util.UUID
        get() = gattDescriptor.uuid
    override val value: ByteArray?
        get() = gattDescriptor.value
    override fun updateValue(value: ByteArray?) {
        gattDescriptor.value = value
    }
    override val permissions: Int
        get() = gattDescriptor.permissions
    override val characteristic: CharacteristicWrapper
        get() = DefaultCharacteristicWrapper(gattDescriptor.characteristic)

    override fun setValue(newValue: ByteArray?): Boolean {
        return gattDescriptor.setValue(newValue)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattService

actual interface ServiceWrapper {

    actual val uuid: java.util.UUID
    val type: Int
    val instanceId: Int
    actual val characteristics: List<CharacteristicWrapper>
    val includedServices: List<ServiceWrapper>

    fun getCharacteristic(uuid: java.util.UUID): CharacteristicWrapper?
    fun addCharacteristic(characteristic: BluetoothGattCharacteristic): Boolean
    fun addService(service: BluetoothGattService): Boolean
}

class DefaultGattServiceWrapper(private val gattService: BluetoothGattService) :
    ServiceWrapper {

    override val uuid: java.util.UUID
        get() = gattService.uuid
    override val type: Int
        get() = gattService.type
    override val instanceId: Int
        get() = gattService.instanceId
    override val characteristics: List<CharacteristicWrapper>
        get() = gattService.characteristics.map { DefaultCharacteristicWrapper(it) }
    override val includedServices: List<ServiceWrapper>
        get() = gattService.includedServices.map { DefaultGattServiceWrapper(it) }

    override fun getCharacteristic(uuid: java.util.UUID): CharacteristicWrapper? {
        return gattService.getCharacteristic(uuid)?.let { DefaultCharacteristicWrapper(it) }
    }

    override fun addCharacteristic(characteristic: BluetoothGattCharacteristic): Boolean {
        return gattService.addCharacteristic(characteristic)
    }

    override fun addService(service: BluetoothGattService): Boolean {
        return gattService.addService(service)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

actual typealias UUID = java.util.UUID

actual val UUID.uuidString: String
    get() = toString()

internal actual fun unsafeUUIDFrom(uuidString: String): UUID =
    if (uuidString.isShortUUID()) uuidFromShort(uuidString)
    else UUID.fromString(uuidString)

actual fun randomUUID(): UUID = UUID.randomUUID()

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

actual typealias Value = ByteArray

actual val Value.asBytes: ByteArray
    get() = this

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import com.splendo.kaluga.bluetooth.device.DeviceAction
import com.splendo.kaluga.bluetooth.device.DeviceState
import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableSharedFlow

abstract class Attribute<R : DeviceAction.Read, W : DeviceAction.Write>(initialValue: ByteArray? = null, protected val stateRepo: DeviceStateFlowRepo) : Flow<ByteArray?> {
    abstract val uuid: UUID

    override suspend fun collect(collector: FlowCollector<ByteArray?>) =
        sharedFlow.collect(collector)

    // TODO make configurable
    private val sharedFlow = MutableSharedFlow<ByteArray?>(0, 256, BufferOverflow.DROP_OLDEST).also { it.tryEmit(initialValue) }

    suspend fun readValue(): DeviceAction {
        val action = createReadAction()
        addAction(action)
        return action
    }

    internal abstract fun createReadAction(): R

    suspend fun writeValue(newValue: ByteArray?): DeviceAction {
        val action = createWriteAction(newValue)
        addAction(action)
        return action
    }

    internal abstract fun createWriteAction(newValue: ByteArray?): W

    open suspend fun updateValue() {
        val nextValue = getUpdatedValue()
        sharedFlow.emit(nextValue)
    }

    internal abstract fun getUpdatedValue(): ByteArray?

    protected suspend fun addAction(action: DeviceAction) {
        stateRepo.takeAndChangeState { state ->
            when (state) {
                is DeviceState.Connected.Idle -> {
                    state.handleAction(action)
                }
                is DeviceState.Connected.HandlingAction -> {
                    state.addAction(action)
                }
                is DeviceState.Connected.NoServices,
                is DeviceState.Connected.Discovering,
                is DeviceState.Connecting,
                is DeviceState.Reconnecting,
                is DeviceState.Disconnected,
                is DeviceState.Disconnecting,
                -> {
                    state.remain() // TODO consider an optional buffer
                }
            }
        }
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import co.touchlab.stately.collections.sharedMutableListOf
import com.splendo.kaluga.bluetooth.device.BaseAdvertisementData
import com.splendo.kaluga.bluetooth.device.ConnectionSettings
import com.splendo.kaluga.bluetooth.device.Device
import com.splendo.kaluga.bluetooth.device.DeviceAction
import com.splendo.kaluga.bluetooth.device.DeviceInfoImpl
import com.splendo.kaluga.bluetooth.device.DeviceState
import com.splendo.kaluga.bluetooth.device.DeviceState.Connected
import com.splendo.kaluga.bluetooth.device.DeviceState.Connecting
import com.splendo.kaluga.bluetooth.device.DeviceState.Disconnected
import com.splendo.kaluga.bluetooth.device.DeviceState.Disconnecting
import com.splendo.kaluga.bluetooth.device.DeviceState.Reconnecting
import com.splendo.kaluga.bluetooth.device.Identifier
import com.splendo.kaluga.bluetooth.scanner.BaseScanner
import com.splendo.kaluga.bluetooth.scanner.ScanningState
import com.splendo.kaluga.bluetooth.scanner.ScanningStateRepo
import com.splendo.kaluga.permissions.Permissions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
import kotlin.jvm.JvmName

interface BluetoothService {
    fun startScanning(filter: Set<UUID> = emptySet())
    fun stopScanning()
    fun devices(): Flow<List<Device>>
    suspend fun isScanning(): StateFlow<Boolean>
    val isEnabled: Flow<Boolean>
}

class Bluetooth internal constructor(
    permissions: Permissions,
    connectionSettings: ConnectionSettings,
    autoRequestPermission: Boolean,
    autoEnableBluetooth: Boolean,
    scannerBuilder: BaseScanner.Builder,
    coroutineScope: CoroutineScope
) : BluetoothService, CoroutineScope by coroutineScope {

    interface Builder {
        fun create(
            connectionSettings: ConnectionSettings = ConnectionSettings(ConnectionSettings.ReconnectionSettings.Always),
            autoRequestPermission: Boolean = true,
            autoEnableBluetooth: Boolean = true,
            coroutineScope: CoroutineScope = MainScope()
        ): Bluetooth
    }

    companion object {
        private const val LOG_TAG = "Kaluga Bluetooth"
    }

    internal val scanningStateRepo = ScanningStateRepo(
        permissions,
        connectionSettings,
        autoRequestPermission,
        autoEnableBluetooth,
        scannerBuilder
    )

    sealed class ScanMode {
        object Stopped : ScanMode()
        class Scan(val filter: Set<UUID>) : ScanMode()
    }

    private val scanMode = MutableStateFlow<ScanMode>(ScanMode.Stopped)

    override fun devices(): Flow<List<Device>> = combine(scanningStateRepo, scanMode) { scanState, scanMode ->
        when (scanState) {
            is ScanningState.Initialized.Enabled.Idle -> when (scanMode) {
                is ScanMode.Scan -> {
                    scanningStateRepo.takeAndChangeState(
                        remainIfStateNot = ScanningState.Initialized.Enabled.Idle::class
                    ) { it.startScanning(scanMode.filter) }
                    if (scanState.discovered.filter == scanMode.filter) {
                        scanState.discovered.devices
                    } else {
                        emptyList()
                    }
                }
                is ScanMode.Stopped -> emptyList()
            }
            is ScanningState.Initialized.Enabled.Scanning -> when (scanMode) {
                is ScanMode.Scan -> {
                    if (scanState.discovered.filter == scanMode.filter) {
                        scanState.discovered.devices
                    } else {
                        scanningStateRepo.takeAndChangeState(
                            remainIfStateNot = ScanningState.Initialized.Enabled.Scanning::class
                        ) { it.stopScanning }
                        emptyList()
                    }
                }
                is ScanMode.Stopped -> {
                    scanningStateRepo.takeAndChangeState(
                        remainIfStateNot = ScanningState.Initialized.Enabled.Scanning::class
                    ) { it.stopScanning }
                    scanState.discovered.devices
                }
            }
            is ScanningState.Initialized.NoBluetooth -> {
                emptyList()
            }
            is ScanningState.NotInitialized -> {
                scanningStateRepo.takeAndChangeState(
                    remainIfStateNot = ScanningState.NotInitialized::class
                ) { it.initialize(scanningStateRepo) }
                emptyList()
            }
        }
    }.distinctUntilChanged()

    override fun startScanning(filter: Set<UUID>) {
        scanMode.value = ScanMode.Scan(filter)
    }

    override fun stopScanning() {
        scanMode.value = ScanMode.Stopped
    }

    override suspend fun isScanning() = combine(scanningStateRepo, scanMode) { scanState, scanMode ->
        scanState is ScanningState.Initialized.Enabled.Scanning && scanMode is ScanMode.Scan
    }.stateIn(this)

    override val isEnabled = scanningStateRepo
        .mapLatest { it is ScanningState.Initialized.Enabled }
}

expect class BluetoothBuilder : Bluetooth.Builder

operator fun Flow<List<Device>>.get(identifier: Identifier): Flow<Device?> {
    return this.map { devices ->
        devices.firstOrNull { it.identifier == identifier }
    }
}

fun Flow<Device?>.state(): Flow<DeviceState> {
    return this.flatMapLatest { device ->
        device ?: emptyFlow()
    }
}

fun Flow<Device?>.services(): Flow<List<Service>> {
    return state().transformLatest { deviceState ->
        emit(
            when (deviceState) {
                is Connected -> {
                    when (deviceState) {
                        is Connected.NoServices -> {
                            deviceState.startDiscovering()
                            emptyList()
                        }
                        is Connected.Idle -> deviceState.services
                        is Connected.HandlingAction -> deviceState.services
                        else -> emptyList()
                    }
                }
                else -> emptyList()
            }
        )
    }.distinctUntilChanged()
}

suspend fun Flow<Device?>.connect() {
    state().transformLatest { deviceState ->
        when (deviceState) {
            is Disconnected -> deviceState.startConnecting()
            is Connected -> emit(Unit)
            is Connecting, is Reconnecting, is Disconnecting -> { }
        }
    }.first()
}

suspend fun Flow<Device?>.disconnect() {
    state().transformLatest { deviceState ->
        when (deviceState) {
            is Connected -> deviceState.startDisconnected()
            is Connecting -> deviceState.handleCancel()
            is Reconnecting -> deviceState.handleCancel()
            is Disconnected -> emit(Unit)
            is Disconnecting -> {} // just wait
        }
    }.first()
}

fun Flow<Device?>.info(): Flow<DeviceInfoImpl> {
    return state().transformLatest { deviceState ->
        emit(deviceState.deviceInfo)
    }
}

fun Flow<Device?>.advertisement(): Flow<BaseAdvertisementData> {
    return this.info().map { it.advertisementData }.distinctUntilChanged()
}

fun Flow<Device?>.rssi(): Flow<Int> {
    return this.info().map { it.rssi }.distinctUntilChanged()
}

fun Flow<Device?>.distance(environmentalFactor: Double = 2.0, averageOver: Int = 5): Flow<Double> {
    val lastNResults = sharedMutableListOf<Double>()
    return this.info().map { deviceInfo ->
        while (lastNResults.size >= averageOver) {
            lastNResults.removeAt(0)
        }
        val distance = deviceInfo.distance(environmentalFactor)
        if (!distance.isNaN())
            lastNResults.add(distance)
        if (lastNResults.isNotEmpty())
            lastNResults.average()
        else
            Double.NaN
    }
}

suspend fun Flow<Device?>.updateRssi() {
    state().transformLatest { deviceState ->
        when (deviceState) {
            is Connected -> {
                deviceState.readRssi()
                emit(Unit)
            }
            else -> {}
        }
    }.first()
}

@JvmName("getService")
operator fun Flow<List<Service>>.get(uuid: UUID): Flow<Service?> {
    return this.map { services ->
        services.firstOrNull {
            it.uuid.uuidString == uuid.uuidString
        }
    }
}

fun Flow<Service?>.characteristics(): Flow<List<Characteristic>> {
    return this.mapLatest { service -> service?.characteristics ?: emptyList() }.distinctUntilChanged()
}

fun Flow<Characteristic?>.descriptors(): Flow<List<Descriptor>> {
    return this.mapLatest { characteristic -> characteristic?.descriptors ?: emptyList() }.distinctUntilChanged()
}

@JvmName("getAttribute")
operator fun <T : Attribute<R, W>, R : DeviceAction.Read, W : DeviceAction.Write> Flow<List<T>>.get(uuid: UUID): Flow<T?> {
    return this.map { attribute ->
        attribute.firstOrNull {
            it.uuid.uuidString == uuid.uuidString
        }
    }
}

fun <T : Attribute<R, W>, R : DeviceAction.Read, W : DeviceAction.Write> Flow<T?>.value(): Flow<ByteArray?> {
    return this.flatMapLatest { attribute ->
        attribute ?: flowOf(null)
    } // TODO: we probably want to read duplicate values so this is for now disabled: .distinctUntilChanged()
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import com.splendo.kaluga.base.ServiceMonitor

expect interface BluetoothMonitor : ServiceMonitor {

    class Builder {
        fun create(): BluetoothMonitor
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import co.touchlab.stately.concurrency.AtomicBoolean
import com.splendo.kaluga.bluetooth.device.DeviceAction
import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first

open class Characteristic(val wrapper: CharacteristicWrapper, initialValue: ByteArray? = null, stateRepo: DeviceStateFlowRepo) : Attribute<DeviceAction.Read.Characteristic, DeviceAction.Write.Characteristic>(initialValue, stateRepo) {

    private val isBusy = MutableStateFlow(false)
    private val _isNotifying = AtomicBoolean(false)
    var isNotifying: Boolean
        get() = _isNotifying.value
        set(value) { _isNotifying.value = value }

    /**
     * Enables notification or indication for this [Characteristic].
     *
     * Creates and puts [DeviceAction.Notification.Enable] into queue to be executed.
     * Sets [isNotifying] to `true` after action completed successfully.
     *
     * @return [DeviceAction] if action was added to the queue, or
     * `null` if notification is already enabled.
     * @see [disableNotification]
     * @see [isNotifying]
     */
    suspend fun enableNotification(): DeviceAction? {

        do {
            isBusy.first { !it }
            if (isNotifying) return null
        } while (!isBusy.compareAndSet(expect = false, update = true))

        val action = createNotificationAction(enabled = true)
        addAction(action)
        action.completedSuccessfully.invokeOnCompletion {
            if (it == null && action.completedSuccessfully.getCompleted()) {
                isNotifying = true
            }
            isBusy.compareAndSet(expect = true, update = false)
        }
        return action
    }

    /**
     * Disables notification or indication for this [Characteristic]
     *
     * Creates and puts [DeviceAction.Notification.Disable] into queue to be executed.
     * Sets [isNotifying] to `false` after action completed successfully.
     *
     * @return [DeviceAction] if action was added to the queue, or
     * `null` if notification is already disabled.
     * @see [enableNotification]
     * @see [isNotifying]
     */
    suspend fun disableNotification(): DeviceAction? {

        do {
            isBusy.first { !it }
            if (!isNotifying) return null
        } while (!isBusy.compareAndSet(expect = false, update = true))

        val action = createNotificationAction(enabled = false)
        addAction(action)
        action.completedSuccessfully.invokeOnCompletion {
            if (it == null && action.completedSuccessfully.getCompleted()) {
                isNotifying = false
            }
            isBusy.compareAndSet(expect = true, update = false)
        }
        return action
    }

    override val uuid = wrapper.uuid

    val descriptors: List<Descriptor> = wrapper.descriptors.map { Descriptor(it, stateRepo = stateRepo) }

    override fun createReadAction(): DeviceAction.Read.Characteristic {
        return DeviceAction.Read.Characteristic(this)
    }

    override fun createWriteAction(newValue: ByteArray?): DeviceAction.Write.Characteristic {
        return DeviceAction.Write.Characteristic(newValue, this)
    }

    private fun createNotificationAction(enabled: Boolean): DeviceAction.Notification {
        return if (enabled) DeviceAction.Notification.Enable(this)
        else DeviceAction.Notification.Disable(this)
    }

    override fun getUpdatedValue(): ByteArray? {
        return wrapper.value?.asBytes
    }
}

expect interface CharacteristicWrapper {
    val uuid: UUID
    val descriptors: List<DescriptorWrapper>
    val value: Value?
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import com.splendo.kaluga.bluetooth.device.DeviceAction
import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo

open class Descriptor(
    val wrapper: DescriptorWrapper,
    initialValue: ByteArray? = null,
    stateRepo: DeviceStateFlowRepo
) : Attribute<DeviceAction.Read.Descriptor, DeviceAction.Write.Descriptor>(initialValue, stateRepo) {

    override val uuid = wrapper.uuid

    override fun createReadAction(): DeviceAction.Read.Descriptor {
        return DeviceAction.Read.Descriptor(this)
    }

    override fun createWriteAction(newValue: ByteArray?): DeviceAction.Write.Descriptor {
        return DeviceAction.Write.Descriptor(newValue, this)
    }

    override fun getUpdatedValue(): ByteArray? {
        return wrapper.value?.asBytes
    }
}

expect interface DescriptorWrapper {
    val uuid: UUID
    val value: Value?
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo

class Service(
    service: ServiceWrapper,
    private val stateRepo: DeviceStateFlowRepo
) {
    val uuid = service.uuid
    val characteristics = service.characteristics.map { Characteristic(it, stateRepo = stateRepo) }
}

expect interface ServiceWrapper {
    val characteristics: List<CharacteristicWrapper>
    val uuid: UUID
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.

 */

@file:JvmName("CommonUUID")

package com.splendo.kaluga.bluetooth

import com.splendo.kaluga.base.text.format
import kotlin.jvm.JvmName

private object Constants {
    val formatValidationRegex = Regex(
        "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-f]{4}",
        RegexOption.IGNORE_CASE
    )
    const val baseBluetoothUUID = "0000%s-0000-1000-8000-00805f9b34fb"
}

sealed class UUIDException(message: String?) : Exception(message) {
    class InvalidFormat(uuidString: String) :
        UUIDException("String '$uuidString' does not represent a valid UUID")
}

expect class UUID

expect val UUID.uuidString: String

@Throws(UUIDException::class)
fun uuidFrom(uuidString: String): UUID =
    if (uuidString.isValidUUIDString()) unsafeUUIDFrom(uuidString)
    else throw UUIDException.InvalidFormat(uuidString)

expect fun randomUUID(): UUID

/**
 * The UUID format is the same for all services and characteristics and defined in bluetooth specification.
 *
 * @see GATT Characteristics specification
 * @see Service discovery
 */
internal fun uuidFromShort(uuidString: String): UUID =
    uuidFrom(Constants.baseBluetoothUUID.format(uuidString))

/**
 * Meant for internal usage. It takes string which already passed validation
 */
internal expect fun unsafeUUIDFrom(uuidString: String): UUID

internal fun String.isShortUUID() = length == 4

internal fun String.isValidUUIDString(): Boolean = Constants.formatValidationRegex.matches(this)

/**
 * This function can be used to generate UUID on platforms which don't support it out of the box
 */
internal fun randomUUIDString(): String {
    val alphabet = ('A'..'F') + ('0'..'9')
    fun randomBlock(size: Int) = List(size) { alphabet.random() }.joinToString("")

    return "${randomBlock(8)}-${randomBlock(4)}-${randomBlock(4)}-${randomBlock(4)}-${randomBlock(12)}"
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

expect class Value

expect val Value.asBytes: ByteArray

package com.splendo.kaluga.bluetooth

import com.splendo.kaluga.bluetooth.device.ConnectionSettings
import com.splendo.kaluga.bluetooth.scanner.Scanner
import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermission
import kotlinx.coroutines.CoroutineScope
import platform.Foundation.NSBundle

actual class BluetoothBuilder(
    private val bundle: NSBundle = NSBundle.mainBundle,
    private val permissions: Permissions = Permissions(
        PermissionsBuilder(bundle).apply {
            registerBluetoothPermission()
        }
    )
) : Bluetooth.Builder {

    override fun create(connectionSettings: ConnectionSettings, autoRequestPermission: Boolean, autoEnableBluetooth: Boolean, coroutineScope: CoroutineScope): Bluetooth {
        return Bluetooth(permissions, connectionSettings, autoRequestPermission, autoEnableBluetooth, Scanner.Builder(), coroutineScope)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import com.splendo.kaluga.base.DefaultServiceMonitor
import com.splendo.kaluga.base.ServiceMonitor
import platform.CoreBluetooth.CBCentralManager
import platform.CoreBluetooth.CBCentralManagerDelegateProtocol
import platform.CoreBluetooth.CBCentralManagerStatePoweredOn
import platform.darwin.NSObject

actual interface BluetoothMonitor : ServiceMonitor {
    actual class Builder(
        private val centralManager: CBCentralManager = CBCentralManager()
    ) {
        actual fun create(): BluetoothMonitor = DefaultBluetoothMonitor(centralManager = centralManager)
    }
}

class DefaultBluetoothMonitor internal constructor(
    private val centralManager: CBCentralManager
) : DefaultServiceMonitor(), BluetoothMonitor {

    internal class CentralManagerDelegate(
        private val updateEnabledState: () -> Unit
    ) : NSObject(), CBCentralManagerDelegateProtocol {
        override fun centralManagerDidUpdateState(central: CBCentralManager) {
            updateEnabledState()
        }
    }

    private val centralManagerDelegate = CentralManagerDelegate(::updateState)
    override val isServiceEnabled: Boolean
        get() = centralManager.state == CBCentralManagerStatePoweredOn

    override fun startMonitoring() {
        super.startMonitoring()
        centralManager.delegate = centralManagerDelegate
    }

    override fun stopMonitoring() {
        super.stopMonitoring()
        centralManager.delegate = null
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import com.splendo.kaluga.base.typedList
import platform.CoreBluetooth.CBCharacteristic
import platform.CoreBluetooth.CBCharacteristicWriteWithResponse
import platform.CoreBluetooth.CBDescriptor
import platform.CoreBluetooth.CBPeripheral
import platform.CoreBluetooth.CBUUID
import platform.Foundation.NSData

actual interface CharacteristicWrapper {
    actual val uuid: CBUUID
    actual val descriptors: List<DescriptorWrapper>
    actual val value: NSData?

    fun readValue(peripheral: CBPeripheral)
    fun writeValue(value: NSData, peripheral: CBPeripheral)
    fun setNotificationValue(enabled: Boolean, peripheral: CBPeripheral)
}

class DefaultCharacteristicWrapper(private val characteristic: CBCharacteristic) : CharacteristicWrapper {

    override val uuid: CBUUID get() { return characteristic.UUID }
    override val descriptors: List<DescriptorWrapper> = characteristic.descriptors?.typedList<CBDescriptor>()?.map { DefaultDescriptorWrapper(it) } ?: emptyList()
    override val value: NSData? get() { return characteristic.value }

    override fun readValue(peripheral: CBPeripheral) {
        peripheral.readValueForCharacteristic(characteristic)
    }

    override fun writeValue(value: NSData, peripheral: CBPeripheral) {
        peripheral.writeValue(value, characteristic, CBCharacteristicWriteWithResponse)
    }

    override fun setNotificationValue(enabled: Boolean, peripheral: CBPeripheral) {
        peripheral.setNotifyValue(enabled, characteristic)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import com.splendo.kaluga.base.toNSData
import platform.CoreBluetooth.CBDescriptor
import platform.CoreBluetooth.CBPeripheral
import platform.CoreBluetooth.CBUUID
import platform.CoreBluetooth.CBUUIDCharacteristicExtendedPropertiesString
import platform.CoreBluetooth.CBUUIDCharacteristicFormatString
import platform.CoreBluetooth.CBUUIDCharacteristicUserDescriptionString
import platform.CoreBluetooth.CBUUIDClientCharacteristicConfigurationString
import platform.CoreBluetooth.CBUUIDServerCharacteristicConfigurationString
import platform.Foundation.NSData
import platform.Foundation.NSNumber
import platform.Foundation.NSString
import platform.Foundation.NSUTF8StringEncoding
import platform.Foundation.dataUsingEncoding

actual interface DescriptorWrapper {
    actual val uuid: CBUUID
    actual val value: NSData?

    fun readValue(peripheral: CBPeripheral)
    fun writeValue(value: NSData, peripheral: CBPeripheral)
}

class DefaultDescriptorWrapper(private val descriptor: CBDescriptor) : DescriptorWrapper {

    override val uuid: CBUUID get() { return descriptor.UUID }
    override val value: NSData? get() {
        return when (descriptor.UUID.uuidString) {
            CBUUIDCharacteristicFormatString -> {
                descriptor.value as? NSData
            }
            CBUUIDCharacteristicUserDescriptionString -> {
                (descriptor.value as? NSString)?.dataUsingEncoding(NSUTF8StringEncoding)
            }
            CBUUIDCharacteristicExtendedPropertiesString -> {
                (descriptor.value as? NSNumber)?.let {
                    byteArrayOf(it.shortValue.toByte()).toNSData()
                }
            }
            CBUUIDClientCharacteristicConfigurationString -> {
                (descriptor.value as? NSNumber)?.let {
                    byteArrayOf(it.shortValue.toByte()).toNSData()
                }
            }
            CBUUIDServerCharacteristicConfigurationString -> {
                (descriptor.value as? NSNumber)?.let {
                    byteArrayOf(it.shortValue.toByte()).toNSData()
                }
            }
            else -> descriptor.value as? NSData
        }
    }

    override fun readValue(peripheral: CBPeripheral) {
        peripheral.readValueForDescriptor(descriptor)
    }

    override fun writeValue(value: NSData, peripheral: CBPeripheral) {
        peripheral.writeValue(value, descriptor)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import com.splendo.kaluga.base.typedList
import platform.CoreBluetooth.CBCharacteristic
import platform.CoreBluetooth.CBService
import platform.CoreBluetooth.CBUUID

actual interface ServiceWrapper {
    actual val uuid: CBUUID
    actual val characteristics: List<CharacteristicWrapper>
}

class DefaultServiceWrapper(service: CBService) : ServiceWrapper {

    override val uuid: CBUUID = service.UUID
    override val characteristics: List<CharacteristicWrapper> = service.characteristics?.typedList<CBCharacteristic>()?.map { DefaultCharacteristicWrapper(it) } ?: emptyList()
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import platform.CoreBluetooth.CBUUID
import platform.Foundation.NSUUID

actual typealias UUID = CBUUID

actual val UUID.uuidString: String
    get() = UUIDString

internal actual fun unsafeUUIDFrom(uuidString: String) =
    CBUUID.UUIDWithString(uuidString)

actual fun randomUUID(): UUID {
    return CBUUID.UUIDWithNSUUID(NSUUID.UUID())
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import com.splendo.kaluga.base.toByteArray
import platform.Foundation.NSData

actual typealias Value = NSData

@Suppress("EXTENSION_SHADOWED_BY_MEMBER") // not relevant from Common
actual val Value.asBytes: ByteArray
    get() = this.toByteArray()

package com.splendo.kaluga.bluetooth

import com.splendo.kaluga.bluetooth.device.ConnectionSettings
import com.splendo.kaluga.bluetooth.scanner.Scanner
import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermission
import kotlinx.coroutines.CoroutineScope

actual class BluetoothBuilder(
    private val permissions: Permissions = Permissions(
        PermissionsBuilder().apply {
            registerBluetoothPermission()
        }
    )
) : Bluetooth.Builder {

    override fun create(connectionSettings: ConnectionSettings, autoRequestPermission: Boolean, autoEnableBluetooth: Boolean, coroutineScope: CoroutineScope): Bluetooth {
        return Bluetooth(permissions, connectionSettings, autoRequestPermission, autoEnableBluetooth, Scanner.Builder(), coroutineScope)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import com.splendo.kaluga.base.DefaultServiceMonitor
import com.splendo.kaluga.base.ServiceMonitor

actual interface BluetoothMonitor : ServiceMonitor {
    actual class Builder {
        actual fun create(): BluetoothMonitor = DefaultBluetoothMonitor()
    }
}

class DefaultBluetoothMonitor : DefaultServiceMonitor(), BluetoothMonitor {

    override val isServiceEnabled: Boolean
        get() = TODO("Not yet implemented")
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

actual data class UUID(val uuidString: String)

actual val UUID.uuidString: String
    get() = uuidString

internal actual fun unsafeUUIDFrom(uuidString: String): UUID = UUID(uuidString = uuidString)

actual fun randomUUID(): UUID = UUID(randomUUIDString())

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

actual typealias Value = ByteArray

actual val Value.asBytes: ByteArray
    get() = this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

actual interface CharacteristicWrapper {
    actual val uuid: UUID
    actual val descriptors: List<DescriptorWrapper>
    actual val value: Value?
}

actual interface DescriptorWrapper {
    actual val uuid: UUID
    actual val value: Value?
}

actual interface ServiceWrapper {
    actual val characteristics: List<CharacteristicWrapper>
    actual val uuid: UUID
}

package com.splendo.kaluga.bluetooth

import com.splendo.kaluga.bluetooth.device.ConnectionSettings
import com.splendo.kaluga.bluetooth.scanner.Scanner
import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermission
import kotlinx.coroutines.CoroutineScope

actual class BluetoothBuilder(
    private val permissions: Permissions = Permissions(
        PermissionsBuilder().apply {
            registerBluetoothPermission()
        }
    )
) : Bluetooth.Builder {

    override fun create(connectionSettings: ConnectionSettings, autoRequestPermission: Boolean, autoEnableBluetooth: Boolean, coroutineScope: CoroutineScope): Bluetooth {
        return Bluetooth(permissions, connectionSettings, autoRequestPermission, autoEnableBluetooth, Scanner.Builder(), coroutineScope)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

import com.splendo.kaluga.base.DefaultServiceMonitor
import com.splendo.kaluga.base.ServiceMonitor

actual interface BluetoothMonitor : ServiceMonitor {
    actual class Builder {
        actual fun create(): BluetoothMonitor = DefaultBluetoothMonitor()
    }
}

class DefaultBluetoothMonitor : DefaultServiceMonitor(), BluetoothMonitor {

    override val isServiceEnabled: Boolean
        get() = TODO("Not yet implemented")
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

actual data class UUID(val uuidString: String)

actual val UUID.uuidString: String
    get() = uuidString

internal actual fun unsafeUUIDFrom(uuidString: String): UUID = UUID(uuidString = uuidString)

actual fun randomUUID(): UUID = UUID(randomUUIDString())

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

actual typealias Value = ByteArray

actual val Value.asBytes: ByteArray
    get() = this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth

actual interface CharacteristicWrapper {
    actual val uuid: UUID
    actual val descriptors: List<DescriptorWrapper>
    actual val value: Value?
}

actual interface DescriptorWrapper {
    actual val uuid: UUID
    actual val value: Value?
}

actual interface ServiceWrapper {
    actual val characteristics: List<CharacteristicWrapper>
    actual val uuid: UUID
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.calendar

import android.Manifest
import android.content.Context
import com.splendo.kaluga.permissions.AndroidPermissionsManager
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class CalendarPermissionManager(
    context: Context,
    actual val calendar: CalendarPermission,
    stateRepo: CalendarPermissionStateRepo
) : PermissionManager<CalendarPermission>(stateRepo) {

    private val permissionsManager = AndroidPermissionsManager(context, this, if (calendar.allowWrite) arrayOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR) else arrayOf(Manifest.permission.READ_CALENDAR))

    override suspend fun requestPermission() {
        permissionsManager.requestPermissions()
    }

    override suspend fun initializeState(): PermissionState<CalendarPermission> {
        return when {
            permissionsManager.hasPermissions -> PermissionState.Allowed()
            else -> PermissionState.Denied.Requestable()
        }
    }

    override suspend fun startMonitoring(interval: Long) {
        permissionsManager.startMonitoring(interval)
    }

    override suspend fun stopMonitoring() {
        permissionsManager.stopMonitoring()
    }
}

actual class CalendarPermissionManagerBuilder actual constructor(private val context: PermissionContext) : BaseCalendarPermissionManagerBuilder {

    override fun create(calendar: CalendarPermission, repo: CalendarPermissionStateRepo): PermissionManager<CalendarPermission> {
        return CalendarPermissionManager(context.context, calendar, repo)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.calendar

import com.splendo.kaluga.permissions.BasePermissionsBuilder
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionStateRepo
import com.splendo.kaluga.permissions.defaultPermissionContext
import kotlin.coroutines.CoroutineContext

/**
 * A [PermissionManager] for managing [CalendarPermission]
 */
expect class CalendarPermissionManager : PermissionManager<CalendarPermission> {
    /**
     * The [CalendarPermission] managed by this manager
     */
    val calendar: CalendarPermission
}

interface BaseCalendarPermissionManagerBuilder : BasePermissionsBuilder {

    /**
     * Creates a [CalendarPermissionManager]
     * @param repo The [CalendarPermissionStateRepo] associated with the [CalendarPermission]
     */
    fun create(calendar: CalendarPermission, repo: CalendarPermissionStateRepo): PermissionManager<CalendarPermission>
}

/**
 * A builder for creating a [CalendarPermissionManager]
 */
expect class CalendarPermissionManagerBuilder(context: PermissionContext = defaultPermissionContext) : BaseCalendarPermissionManagerBuilder

/**
 * A [PermissionStateRepo] for [CalendarPermission]
 * @param builder The [CalendarPermissionManagerBuilder] for creating the [CalendarPermissionManager] associated with the permission
 * @param coroutineContext The [CoroutineContext] to run the state machine on.
 */
class CalendarPermissionStateRepo(calendar: CalendarPermission, builder: BaseCalendarPermissionManagerBuilder, coroutineContext: CoroutineContext) : PermissionStateRepo<CalendarPermission>(coroutineContext = coroutineContext) {

    override val permissionManager: PermissionManager<CalendarPermission> = builder.create(calendar, this)
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.calendar

import com.splendo.kaluga.permissions.Permission
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.defaultPermissionContext

/**
 * Permission to access the users Calendar
 * @param allowWrite If `true` writing to the calendar is permitted
 */
data class CalendarPermission(val allowWrite: Boolean = false) : Permission()

fun PermissionsBuilder.registerCalendarPermission() =
    registerCalendarPermissionBuilder(context).also { builder ->
        registerPermissionStateRepoBuilder(CalendarPermission::class) { permission, coroutineContext ->
            CalendarPermissionStateRepo(permission as CalendarPermission, builder as BaseCalendarPermissionManagerBuilder, coroutineContext)
        }
    }

internal fun PermissionsBuilder.registerCalendarPermissionBuilder(context: PermissionContext = defaultPermissionContext): CalendarPermissionManagerBuilder = register(
    builder = CalendarPermissionManagerBuilder(context),
    permission = CalendarPermission::class
)

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.calendar

import com.splendo.kaluga.base.mainContinuation
import com.splendo.kaluga.logging.debug
import com.splendo.kaluga.logging.error
import com.splendo.kaluga.permissions.IOSPermissionsHelper
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionRefreshScheduler
import com.splendo.kaluga.permissions.PermissionState
import platform.EventKit.EKAuthorizationStatus
import platform.EventKit.EKAuthorizationStatusAuthorized
import platform.EventKit.EKAuthorizationStatusDenied
import platform.EventKit.EKAuthorizationStatusNotDetermined
import platform.EventKit.EKAuthorizationStatusRestricted
import platform.EventKit.EKEntityType
import platform.EventKit.EKEventStore
import platform.Foundation.NSBundle

const val NSCalendarsUsageDescription = "NSCalendarsUsageDescription"

actual class CalendarPermissionManager(
    private val bundle: NSBundle,
    actual val calendar: CalendarPermission,
    stateRepo: CalendarPermissionStateRepo
) : PermissionManager<CalendarPermission>(stateRepo) {

    private val eventStore = EKEventStore()
    private val authorizationStatus = suspend {
        EKEventStore.authorizationStatusForEntityType(EKEntityType.EKEntityTypeEvent).toAuthorizationStatus()
    }
    private var timerHelper = PermissionRefreshScheduler(this, authorizationStatus)

    override suspend fun requestPermission() {
        if (IOSPermissionsHelper.missingDeclarationsInPList(bundle, NSCalendarsUsageDescription).isEmpty()) {
            timerHelper.isWaiting.value = true
            eventStore.requestAccessToEntityType(
                EKEntityType.EKEntityTypeEvent,
                mainContinuation { success, error ->
                    timerHelper.isWaiting.value = false
                    error?.let {
                        debug(it.localizedDescription)
                        revokePermission(true)
                    } ?: run {
                        if (success) grantPermission() else revokePermission(true)
                    }
                }
            )
        } else {
            revokePermission(true)
        }
    }

    override suspend fun initializeState(): PermissionState<CalendarPermission> {
        return IOSPermissionsHelper.getPermissionState(authorizationStatus())
    }

    override suspend fun startMonitoring(interval: Long) {
        timerHelper.startMonitoring(interval)
    }

    override suspend fun stopMonitoring() {
        timerHelper.stopMonitoring()
    }
}

actual class CalendarPermissionManagerBuilder actual constructor(private val context: PermissionContext) : BaseCalendarPermissionManagerBuilder {

    override fun create(calendar: CalendarPermission, repo: CalendarPermissionStateRepo): PermissionManager<CalendarPermission> {
        return CalendarPermissionManager(context, calendar, repo)
    }
}

private fun EKAuthorizationStatus.toAuthorizationStatus(): IOSPermissionsHelper.AuthorizationStatus {
    return when (this) {
        EKAuthorizationStatusAuthorized -> IOSPermissionsHelper.AuthorizationStatus.Authorized
        EKAuthorizationStatusDenied -> IOSPermissionsHelper.AuthorizationStatus.Denied
        EKAuthorizationStatusRestricted -> IOSPermissionsHelper.AuthorizationStatus.Restricted
        EKAuthorizationStatusNotDetermined -> IOSPermissionsHelper.AuthorizationStatus.NotDetermined
        else -> {
            error(
                "CalendarPermissionManager",
                "Unknown CBManagerAuthorization status={$this}"
            )
            IOSPermissionsHelper.AuthorizationStatus.NotDetermined
        }
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.calendar

import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class CalendarPermissionManager(actual val calendar: CalendarPermission, repo: CalendarPermissionStateRepo) : PermissionManager<CalendarPermission>(repo) {

    override suspend fun requestPermission() {
        TODO("not implemented")
    }

    override suspend fun initializeState(): PermissionState<CalendarPermission> {
        TODO("not implemented")
    }

    override suspend fun startMonitoring(interval: Long) {
        TODO("not implemented")
    }

    override suspend fun stopMonitoring() {
        TODO("not implemented")
    }
}

actual class CalendarPermissionManagerBuilder actual constructor(context: PermissionContext) : BaseCalendarPermissionManagerBuilder {

    override fun create(calendar: CalendarPermission, repo: CalendarPermissionStateRepo): PermissionManager<CalendarPermission> {
        return CalendarPermissionManager(calendar, repo)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.camera

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import com.splendo.kaluga.permissions.AndroidPermissionsManager
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class CameraPermissionManager(
    context: Context,
    stateRepo: CameraPermissionStateRepo
) : PermissionManager<CameraPermission>(stateRepo) {

    private val permissionsManager = AndroidPermissionsManager(context, this, arrayOf(Manifest.permission.CAMERA))
    private val supported = context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)

    override suspend fun requestPermission() {
        if (supported)
            permissionsManager.requestPermissions()
    }

    override suspend fun initializeState(): PermissionState<CameraPermission> {
        return when {
            !supported -> PermissionState.Denied.Locked()
            permissionsManager.hasPermissions -> PermissionState.Allowed()
            else -> PermissionState.Denied.Requestable()
        }
    }

    override suspend fun startMonitoring(interval: Long) {
        if (supported)
            permissionsManager.startMonitoring(interval)
    }

    override suspend fun stopMonitoring() {
        if (supported)
            permissionsManager.stopMonitoring()
    }
}

actual class CameraPermissionManagerBuilder actual constructor(private val context: PermissionContext) : BaseCameraPermissionManagerBuilder {

    override fun create(repo: CameraPermissionStateRepo): PermissionManager<CameraPermission> {
        return CameraPermissionManager(context.context, repo)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.camera

import com.splendo.kaluga.permissions.BasePermissionsBuilder
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionStateRepo
import com.splendo.kaluga.permissions.defaultPermissionContext
import kotlin.coroutines.CoroutineContext

/**
 * A [PermissionManager] for managing [CameraPermission]
 */
expect class CameraPermissionManager : PermissionManager<CameraPermission>

interface BaseCameraPermissionManagerBuilder : BasePermissionsBuilder {

    /**
     * Creates a [CameraPermissionManager]
     * @param repo The [CameraPermissionStateRepo] associated with the [CameraPermission]
     */
    fun create(repo: CameraPermissionStateRepo): PermissionManager<CameraPermission>
}

/**
 * A builder for creating a [CameraPermissionManager]
 */
expect class CameraPermissionManagerBuilder(context: PermissionContext = defaultPermissionContext) : BaseCameraPermissionManagerBuilder

/**
 * A [PermissionStateRepo] for [CameraPermission]
 * @param builder The [CameraPermissionManagerBuilder] for creating the [CameraPermissionManager] associated with the permission
 * @param coroutineContext The [CoroutineContext] to run the state machine on.
 */
class CameraPermissionStateRepo(builder: BaseCameraPermissionManagerBuilder, coroutineContext: CoroutineContext) : PermissionStateRepo<CameraPermission>(coroutineContext = coroutineContext) {

    override val permissionManager: PermissionManager<CameraPermission> = builder.create(this)
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.camera

import com.splendo.kaluga.permissions.Permission
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.defaultPermissionContext

/**
 * Permission to access the users Camera
 */
object CameraPermission : Permission()

fun PermissionsBuilder.registerCameraPermission() =
    registerCameraPermissionBuilder(context).also { builder ->
        registerPermissionStateRepoBuilder(CameraPermission::class) { _, coroutineContext ->
            CameraPermissionStateRepo(builder as BaseCameraPermissionManagerBuilder, coroutineContext)
        }
    }

internal fun PermissionsBuilder.registerCameraPermissionBuilder(context: PermissionContext = defaultPermissionContext) = register(
    builder = CameraPermissionManagerBuilder(context),
    permission = CameraPermission::class
)

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.camera

import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState
import com.splendo.kaluga.permissions.av.AVPermissionHelper
import com.splendo.kaluga.permissions.av.AVTypeCamera
import platform.Foundation.NSBundle

actual class CameraPermissionManager(
    private val bundle: NSBundle,
    stateRepo: CameraPermissionStateRepo
) : PermissionManager<CameraPermission>(stateRepo) {

    private val avPermissionHelper = AVPermissionHelper(bundle, AVTypeCamera(this))

    override suspend fun requestPermission() {
        avPermissionHelper.requestPermission()
    }

    override suspend fun initializeState(): PermissionState<CameraPermission> {
        return avPermissionHelper.initializeState()
    }

    override suspend fun startMonitoring(interval: Long) {
        avPermissionHelper.startMonitoring(interval)
    }

    override suspend fun stopMonitoring() {
        avPermissionHelper.stopMonitoring()
    }
}

actual class CameraPermissionManagerBuilder actual constructor(private val context: PermissionContext) : BaseCameraPermissionManagerBuilder {

    override fun create(repo: CameraPermissionStateRepo): PermissionManager<CameraPermission> {
        return CameraPermissionManager(context, repo)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.camera

import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class CameraPermissionManager(repo: CameraPermissionStateRepo) : PermissionManager<CameraPermission>(repo) {

    override suspend fun requestPermission() {
        TODO("not implemented")
    }

    override suspend fun initializeState(): PermissionState<CameraPermission> {
        TODO("not implemented")
    }

    override suspend fun startMonitoring(interval: Long) {
        TODO("not implemented")
    }

    override suspend fun stopMonitoring() {
        TODO("not implemented")
    }
}

actual class CameraPermissionManagerBuilder actual constructor(context: PermissionContext) : BaseCameraPermissionManagerBuilder {

    override fun create(repo: CameraPermissionStateRepo): PermissionManager<CameraPermission> {
        return CameraPermissionManager(repo)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.camera

import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class CameraPermissionManager(repo: CameraPermissionStateRepo) : PermissionManager<CameraPermission>(repo) {

    override suspend fun requestPermission() {
        TODO("not implemented")
    }

    override suspend fun initializeState(): PermissionState<CameraPermission> {
        TODO("not implemented")
    }

    override suspend fun startMonitoring(interval: Long) {
        TODO("not implemented")
    }

    override suspend fun stopMonitoring() {
        TODO("not implemented")
    }
}

actual class CameraPermissionManagerBuilder actual constructor(context: PermissionContext) : BaseCameraPermissionManagerBuilder {

    override fun create(repo: CameraPermissionStateRepo): PermissionManager<CameraPermission> {
        return CameraPermissionManager(repo)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.contacts

import android.Manifest
import android.content.Context
import com.splendo.kaluga.permissions.AndroidPermissionsManager
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class ContactsPermissionManager(
    context: Context,
    actual val contacts: ContactsPermission,
    stateRepo: ContactsPermissionStateRepo
) : PermissionManager<ContactsPermission>(stateRepo) {

    private val permissionsManager = AndroidPermissionsManager(context, this, if (contacts.allowWrite) arrayOf(Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS) else arrayOf(Manifest.permission.READ_CONTACTS))

    override suspend fun requestPermission() {
        permissionsManager.requestPermissions()
    }

    override suspend fun initializeState(): PermissionState<ContactsPermission> {
        return when {
            permissionsManager.hasPermissions -> PermissionState.Allowed()
            else -> PermissionState.Denied.Requestable()
        }
    }

    override suspend fun startMonitoring(interval: Long) {
        permissionsManager.startMonitoring(interval)
    }

    override suspend fun stopMonitoring() {
        permissionsManager.stopMonitoring()
    }
}

actual class ContactsPermissionManagerBuilder actual constructor(private val context: PermissionContext) : BaseContactsPermissionManagerBuilder {

    override fun create(contacts: ContactsPermission, repo: ContactsPermissionStateRepo): PermissionManager<ContactsPermission> {
        return ContactsPermissionManager(context.context, contacts, repo)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.contacts

import com.splendo.kaluga.permissions.BasePermissionsBuilder
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionStateRepo
import com.splendo.kaluga.permissions.defaultPermissionContext
import kotlin.coroutines.CoroutineContext

/**
 * A [PermissionManager] for managing [ContactsPermission]
 */
expect class ContactsPermissionManager : PermissionManager<ContactsPermission> {
    /**
     * The [Permission.Contacts] managed by this manager
     */
    val contacts: ContactsPermission
}

interface BaseContactsPermissionManagerBuilder : BasePermissionsBuilder {

    /**
     * Creates a [ContactsPermissionManager]
     * @param repo The [ContactsPermissionStateRepo] associated with the [ContactsPermission]
     */
    fun create(contacts: ContactsPermission, repo: ContactsPermissionStateRepo): PermissionManager<ContactsPermission>
}

/**
 * A builder for creating a [ContactsPermissionManager]
 */
expect class ContactsPermissionManagerBuilder(context: PermissionContext = defaultPermissionContext) : BaseContactsPermissionManagerBuilder

/**
 * A [PermissionStateRepo] for [ContactsPermission]
 * @param builder The [ContactsPermissionManagerBuilder] for creating the [ContactsPermissionManager] associated with the permission
 * @param coroutineContext The [CoroutineContext] to run the state machine on.
 */
class ContactsPermissionStateRepo(contacts: ContactsPermission, builder: BaseContactsPermissionManagerBuilder, coroutineContext: CoroutineContext) : PermissionStateRepo<ContactsPermission>(coroutineContext = coroutineContext) {

    override val permissionManager: PermissionManager<ContactsPermission> = builder.create(contacts, this)
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.contacts

import com.splendo.kaluga.permissions.Permission
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.defaultPermissionContext

/**
 * Permission to access the users Contacts
 * @param allowWrite If `true` writing to the contacts is permitted
 */
data class ContactsPermission(val allowWrite: Boolean = false) : Permission()

fun PermissionsBuilder.registerContactsPermission() =
    registerContactsPermissionBuilder(context).also { builder ->
        registerPermissionStateRepoBuilder(ContactsPermission::class) { permission, coroutineContext ->
            ContactsPermissionStateRepo(permission as ContactsPermission, builder as BaseContactsPermissionManagerBuilder, coroutineContext)
        }
    }

internal fun PermissionsBuilder.registerContactsPermissionBuilder(context: PermissionContext = defaultPermissionContext): ContactsPermissionManagerBuilder = register(
    builder = ContactsPermissionManagerBuilder(context),
    permission = ContactsPermission::class
)

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.contacts

import com.splendo.kaluga.base.mainContinuation
import com.splendo.kaluga.logging.debug
import com.splendo.kaluga.logging.error
import com.splendo.kaluga.permissions.IOSPermissionsHelper
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionRefreshScheduler
import com.splendo.kaluga.permissions.PermissionState
import platform.Contacts.CNAuthorizationStatus
import platform.Contacts.CNAuthorizationStatusAuthorized
import platform.Contacts.CNAuthorizationStatusDenied
import platform.Contacts.CNAuthorizationStatusNotDetermined
import platform.Contacts.CNAuthorizationStatusRestricted
import platform.Contacts.CNContactStore
import platform.Contacts.CNEntityType
import platform.Foundation.NSBundle

const val NSContactsUsageDescription = "NSContactsUsageDescription"

actual class ContactsPermissionManager(
    private val bundle: NSBundle,
    actual val contacts: ContactsPermission,
    stateRepo: ContactsPermissionStateRepo
) : PermissionManager<ContactsPermission>(stateRepo) {

    private val contactStore = CNContactStore()
    private val authorizationStatus = suspend {
        CNContactStore.authorizationStatusForEntityType(CNEntityType.CNEntityTypeContacts).toAuthorizationStatus()
    }
    private var timerHelper = PermissionRefreshScheduler(this, authorizationStatus)

    override suspend fun requestPermission() {
        if (IOSPermissionsHelper.missingDeclarationsInPList(bundle, NSContactsUsageDescription).isEmpty()) {
            timerHelper.isWaiting.value = true
            contactStore.requestAccessForEntityType(
                CNEntityType.CNEntityTypeContacts,
                mainContinuation { success, error ->
                    error?.let {
                        debug(it.localizedDescription)
                        revokePermission(true)
                    } ?: run {
                        timerHelper.isWaiting.value = false
                        if (success) grantPermission() else revokePermission(true)
                    }
                }
            )
        } else {
            revokePermission(true)
        }
    }

    override suspend fun initializeState(): PermissionState<ContactsPermission> {
        return IOSPermissionsHelper.getPermissionState(authorizationStatus())
    }

    override suspend fun startMonitoring(interval: Long) {
        timerHelper.startMonitoring(interval)
    }

    override suspend fun stopMonitoring() {
        timerHelper.stopMonitoring()
    }
}

actual class ContactsPermissionManagerBuilder actual constructor(private val context: PermissionContext) : BaseContactsPermissionManagerBuilder {

    override fun create(contacts: ContactsPermission, repo: ContactsPermissionStateRepo): PermissionManager<ContactsPermission> {
        return ContactsPermissionManager(context, contacts, repo)
    }
}

private fun CNAuthorizationStatus.toAuthorizationStatus(): IOSPermissionsHelper.AuthorizationStatus {
    return when (this) {
        CNAuthorizationStatusAuthorized -> IOSPermissionsHelper.AuthorizationStatus.Authorized
        CNAuthorizationStatusDenied -> IOSPermissionsHelper.AuthorizationStatus.Denied
        CNAuthorizationStatusRestricted -> IOSPermissionsHelper.AuthorizationStatus.Restricted
        CNAuthorizationStatusNotDetermined -> IOSPermissionsHelper.AuthorizationStatus.NotDetermined
        else -> {
            error(
                "ContactsPermissionManager",
                "Unknown ContactManagerAuthorization status={$this}"
            )
            IOSPermissionsHelper.AuthorizationStatus.NotDetermined
        }
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.contacts

import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class ContactsPermissionManager(actual val contacts: ContactsPermission, repo: ContactsPermissionStateRepo) : PermissionManager<ContactsPermission>(repo) {

    override suspend fun requestPermission() {
        TODO("not implemented")
    }

    override suspend fun initializeState(): PermissionState<ContactsPermission> {
        TODO("not implemented")
    }

    override suspend fun startMonitoring(interval: Long) {
        TODO("not implemented")
    }

    override suspend fun stopMonitoring() {
        TODO("not implemented")
    }
}

actual class ContactsPermissionManagerBuilder actual constructor(context: PermissionContext) : BaseContactsPermissionManagerBuilder {

    override fun create(contacts: ContactsPermission, repo: ContactsPermissionStateRepo): PermissionManager<ContactsPermission> {
        return ContactsPermissionManager(contacts, repo)
    }
}

/*
Copyright 2020 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.datetimepicker

import android.app.AlertDialog
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.content.Context
import android.content.DialogInterface
import androidx.appcompat.app.AppCompatActivity
import com.splendo.kaluga.architecture.lifecycle.LifecycleManagerObserver
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable
import com.splendo.kaluga.architecture.lifecycle.getOrPutAndRemoveOnDestroyFromCache
import com.splendo.kaluga.architecture.lifecycle.lifecycleManagerObserver
import com.splendo.kaluga.base.utils.Date
import com.splendo.kaluga.base.utils.uses24HourClock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch

actual class DateTimePickerPresenter(
    private val dateTimePicker: DateTimePicker,
    private val themeResourceId: Int,
    private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver(),
    coroutineScope: CoroutineScope
) : BaseDateTimePickerPresenter(dateTimePicker), CoroutineScope by coroutineScope {

    actual class Builder(
        private val themeResourceId: Int = 0,
        private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver()
    ) : BaseDateTimePickerPresenter.Builder(), LifecycleSubscribable by lifecycleManagerObserver {
        actual override fun create(coroutineScope: CoroutineScope) = DateTimePickerPresenter(createDateTimePicker(), themeResourceId, lifecycleManagerObserver, coroutineScope)
    }

    private sealed class DialogPresentation {
        data class Showing(val animated: Boolean, val completion: (Date?) -> Unit) : DialogPresentation()
        object Hidden : DialogPresentation()
    }

    private val presentation = MutableStateFlow<DialogPresentation>(DialogPresentation.Hidden)
    private var alertDialog: AlertDialog? = null

    init {
        launch {
            combine(lifecycleManagerObserver.managerState, presentation) { managerState, dialogPresentation ->
                Pair(managerState, dialogPresentation)
            }.collect { contextPresentation ->
                when (val dialogPresentation = contextPresentation.second) {
                    is DialogPresentation.Showing -> contextPresentation.first?.activity?.let { presentDialog(it, dialogPresentation) } ?: run { alertDialog = null }
                    is DialogPresentation.Hidden -> alertDialog?.dismiss()
                }
            }
        }
    }

    override fun dismissDateTimePicker(animated: Boolean) {
        presentation.value = DialogPresentation.Hidden
    }

    override fun showDateTimePicker(animated: Boolean, completion: (Date?) -> Unit) {
        presentation.value = DialogPresentation.Showing(animated, completion)
    }

    private fun presentDialog(context: Context, presentation: DialogPresentation.Showing) {
        val alertDialog: AlertDialog = when (val type = dateTimePicker.type) {
            is DateTimePicker.Type.TimeType -> TimePickerDialog(
                context,
                themeResourceId,
                { _, hour, minute ->
                    presentation.completion(
                        dateTimePicker.selectedDate.copy().apply {
                            this.hour = hour
                            this.minute = minute
                        }
                    )
                },
                dateTimePicker.selectedDate.hour,
                dateTimePicker.selectedDate.minute,
                dateTimePicker.locale.uses24HourClock
            )
            is DateTimePicker.Type.DateType -> {
                DatePickerDialog(
                    context,
                    themeResourceId,
                    { _, year, month, dayOfMonth ->
                        presentation.completion(
                            dateTimePicker.selectedDate.copy().apply {
                                this.year = year
                                this.month = month + 1
                                this.day = dayOfMonth
                            }
                        )
                    },
                    dateTimePicker.selectedDate.year,
                    dateTimePicker.selectedDate.month - 1,
                    dateTimePicker.selectedDate.day
                ).apply {
                    type.earliestDate?.let {
                        datePicker.minDate = it.millisecondSinceEpoch
                    }
                    type.latestDate?.let {
                        datePicker.maxDate = it.millisecondSinceEpoch
                    }
                }
            }
        }
        alertDialog.apply {
            dateTimePicker.message?.let {
                setMessage(it)
            }
            setButton(
                DialogInterface.BUTTON_POSITIVE,
                dateTimePicker.confirmButtonTitle,
                this as DialogInterface.OnClickListener
            )
            setButton(
                DialogInterface.BUTTON_NEGATIVE,
                dateTimePicker.cancelButtonTitle,
                this as DialogInterface.OnClickListener
            )
            setOnCancelListener { presentation.completion(null) }
            setOnDismissListener {
                this@DateTimePickerPresenter.alertDialog = null
                this@DateTimePickerPresenter.presentation.value = DialogPresentation.Hidden
            }
        }
        this.alertDialog = alertDialog
        alertDialog.show()
    }
}

/**
 * @return The [DateTimePickerPresenter.Builder] which can be used to present alerts while this Activity is active
 * Will be created if need but only one instance will exist.
 *
 * Warning: Do not attempt to use this builder outside of the lifespan of the Activity.
 * Instead, for example use a [com.splendo.kaluga.architecture.viewmodel.ViewModel],
 * which can automatically track which Activity is active for it.
 *
 */
fun AppCompatActivity.datePickerPresenterBuilder(themeResourceId: Int = 0): DateTimePickerPresenter.Builder =
    getOrPutAndRemoveOnDestroyFromCache {
        DateTimePickerPresenter.Builder(themeResourceId, lifecycleManagerObserver())
    }

/*

Copyright 2020 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.datetimepicker

import co.touchlab.stately.concurrency.Lock
import co.touchlab.stately.concurrency.withLock
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker
import com.splendo.kaluga.base.utils.Date
import com.splendo.kaluga.base.utils.Date.Companion.epoch
import com.splendo.kaluga.base.utils.Locale
import com.splendo.kaluga.base.utils.Locale.Companion.defaultLocale
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume

data class DateTimePicker(
    val message: String?,
    val cancelButtonTitle: String,
    val confirmButtonTitle: String,
    val type: Type,
    val locale: Locale,
    val selectedDate: Date
) {

    /**
     * Date Type to select
     */
    sealed class Type {
        /**
         * Selects a Date
         * A range can be provided to limit the dates selectable
         */
        class DateType(
            val earliestDate: Date? = null,
            val latestDate: Date? = null
        ) : Type()

        /**
         * Selects a Time
         */
        object TimeType : Type()
    }
}

/**
 * Interface that defines actions that can be applied to the alert.
 */
interface DateTimePickerActions {
    /**
     * Presents an DateTimePicker
     *
     * @param animated Pass `true` to animate the presentation
     * @param completion The callback invoked when a Date is selected or the dialog is cancelled
     */
    fun showAsync(animated: Boolean = true, completion: (Date?) -> Unit = {})

    /**
     * Presents an DateTimePicker and suspends
     *
     * @param animated
     * @return The [Date] that was selected or `null` if the DateTimePicker was cancelled
     */
    suspend fun show(animated: Boolean = true): Date?

    /**
     * Dismisses the DateTimePicker, which was presented previously
     *
     * @param animated Pass `true` to animate the transition
     */
    fun dismiss(animated: Boolean)
}

/**
 * Abstract DateTimePicker presenter, used to show and dismiss given [DateTimePicker]
 * @see [DateTimePickerPresenter]
 *
 * @property dateTimePicker The alert to present (and dismiss if needed)
 */
abstract class BaseDateTimePickerPresenter(private val dateTimePicker: DateTimePicker) : DateTimePickerActions {

    /**
     * Abstract alert builder class, used to create an [DateTimePicker].
     * The resulting DateTimePicker that can be shown and dismissed using an [DateTimePickerPresenter].
     *
     * @see [DateTimePickerPresenter.Builder]
     */
    abstract class Builder : LifecycleSubscribableMarker {

        private var message: String? = null
        private var cancelButtonTitle: String = ""
        private var confirmButtonTitle: String = ""
        private var locale: Locale = defaultLocale
        private var selectedDate: Date = epoch()
        private var type: DateTimePicker.Type = DateTimePicker.Type.TimeType
        internal val lock = Lock()

        /**
         * Sets the [message] displayed in the DateTimePicker
         *
         * @param message The message of the alert
         */
        fun setMessage(message: String?) = apply { this.message = message }

        /**
         * Sets this [cancelButtonTitle] shown in the DateTimePicker
         */
        fun setCancelButtonTitle(cancelButtonTitle: String) = apply { this.cancelButtonTitle = cancelButtonTitle }

        /**
         * Sets this [cancelButtonTitle] shown in the DateTimePicker
         */
        fun setConfirmButtonTitle(confirmButtonTitle: String) = apply { this.confirmButtonTitle = confirmButtonTitle }

        /**
         * Sets the Locale for which a Date is selected
         */
        fun setLocale(locale: Locale) = apply { this.locale = locale }

        fun setSelectedDate(date: Date) = apply { this.selectedDate = date }

        /**
         * Sets a style of the alert
         *
         * @param type The style of an alert
         */
        internal fun setType(type: DateTimePicker.Type) = apply { this.type = type }

        /**
         * Reset builder into initial state
         */
        internal fun reset() = apply {
            this.message = null
            this.cancelButtonTitle = ""
            this.confirmButtonTitle = ""
            this.locale = defaultLocale
            this.selectedDate = epoch()
            this.type = DateTimePicker.Type.TimeType
        }

        /**
         * Creates a DataTimePicker
         *
         * @return The DateTimePicker object
         * @throws IllegalArgumentException in case missing cancel or confirm titles
         */
        protected fun createDateTimePicker(): DateTimePicker {
            require(cancelButtonTitle.isNotEmpty() && confirmButtonTitle.isNotEmpty()) { "Please set Cancel and Confirm Titles" }

            return DateTimePicker(message, cancelButtonTitle, confirmButtonTitle, type, locale, selectedDate)
        }

        /**
         * Creates the [BaseDateTimePickerPresenter] described by this builder.
         *
         * @param coroutineScope The [CoroutineScope] managing the alert lifecycle.
         * @return The [BaseDateTimePickerPresenter] described by this builder.
         */
        abstract fun create(coroutineScope: CoroutineScope): BaseDateTimePickerPresenter
    }

    override fun showAsync(animated: Boolean, completion: (Date?) -> Unit) {
        showDateTimePicker(animated, completion = completion)
    }

    override suspend fun show(animated: Boolean): Date? =
        suspendCancellableCoroutine { continuation ->
            continuation.invokeOnCancellation {
                dismissDateTimePicker(animated)
                continuation.resume(null)
            }
            showDateTimePicker(animated) { continuation.resume(it) }
        }

    override fun dismiss(animated: Boolean) {
        dismissDateTimePicker(animated)
    }

    protected abstract fun dismissDateTimePicker(animated: Boolean = true)

    protected abstract fun showDateTimePicker(
        animated: Boolean = true,
        completion: (Date?) -> Unit = {}
    )
}

/**
 * Class for presenting an [DateTimePicker].
 */
expect class DateTimePickerPresenter : BaseDateTimePickerPresenter {
    class Builder : BaseDateTimePickerPresenter.Builder {
        /**
         * Creates DateTimePickerPresenter object
         *
         * @param coroutineScope The [CoroutineScope] managing the alert lifecycle.
         * @return The DateTimePickerPresenter object
         */
        override fun create(coroutineScope: CoroutineScope): DateTimePickerPresenter
    }
}

/**
 * Builds an alert using DSL syntax (thread safe)
 *
 * @param coroutineScope The [CoroutineScope] managing the alert lifecycle.
 * @param initialize The block to construct an Alert
 * @return The built alert interface object
 */
fun BaseDateTimePickerPresenter.Builder.buildDatePicker(
    coroutineScope: CoroutineScope,
    earliestDate: Date? = null,
    latestDate: Date? = null,
    initialize: BaseDateTimePickerPresenter.Builder.() -> Unit
): BaseDateTimePickerPresenter = lock.withLock {
    reset()
    setType(DateTimePicker.Type.DateType(earliestDate, latestDate))
    initialize()
    return create(coroutineScope)
}

/**
 * Builds an alert using DSL syntax (thread safe)
 *
 * @param coroutineScope The [CoroutineScope] managing the alert lifecycle.
 * @param initialize The block to construct an Alert
 * @return The built alert interface object
 */
fun BaseDateTimePickerPresenter.Builder.buildTimePicker(coroutineScope: CoroutineScope, initialize: BaseDateTimePickerPresenter.Builder.() -> Unit): BaseDateTimePickerPresenter = lock.withLock {
    reset()
    setType(DateTimePicker.Type.TimeType)
    initialize()
    return create(coroutineScope)
}

/*

Copyright 2020 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.datetimepicker

import com.splendo.kaluga.base.IOSVersion
import com.splendo.kaluga.base.utils.Date
import kotlinx.cinterop.ObjCAction
import kotlinx.coroutines.CoroutineScope
import platform.CoreGraphics.CGFloat
import platform.Foundation.NSCalendar
import platform.Foundation.NSDate
import platform.Foundation.NSTimeInterval
import platform.Foundation.dateWithTimeIntervalSince1970
import platform.Foundation.timeIntervalSince1970
import platform.UIKit.UIButton
import platform.UIKit.UIColor
import platform.UIKit.UIControlEventTouchUpInside
import platform.UIKit.UIDatePicker
import platform.UIKit.UIDatePickerMode
import platform.UIKit.UIDatePickerStyle
import platform.UIKit.UILabel
import platform.UIKit.UIModalPresentationBlurOverFullScreen
import platform.UIKit.UIView
import platform.UIKit.UIViewController
import platform.UIKit.addSubview
import platform.UIKit.backgroundColor
import platform.UIKit.bottomAnchor
import platform.UIKit.centerXAnchor
import platform.UIKit.centerYAnchor
import platform.UIKit.insertSubview
import platform.UIKit.leadingAnchor
import platform.UIKit.systemBackgroundColor
import platform.UIKit.topAnchor
import platform.UIKit.trailingAnchor
import platform.UIKit.translatesAutoresizingMaskIntoConstraints
import platform.objc.sel_registerName

actual class DateTimePickerPresenter(
    private val datePicker: DateTimePicker,
    private val parent: UIViewController
) : BaseDateTimePickerPresenter(datePicker) {

    private inner class DateTimePickerViewController(private val datePicker: DateTimePicker, private val completion: (Date?) -> Unit) : UIViewController(null, null) {

        private lateinit var datePickerView: UIDatePicker

        init {
            this.modalPresentationStyle = UIModalPresentationBlurOverFullScreen
        }

        override fun viewDidLoad() {
            super.viewDidLoad()
            val containerView = UIView().apply {
                backgroundColor = UIColor.systemBackgroundColor
                layer.cornerRadius = 15.0 as CGFloat
                view.addSubview(this)
                translatesAutoresizingMaskIntoConstraints = false
                centerXAnchor.constraintEqualToAnchor(view.centerXAnchor).active = true
                centerYAnchor.constraintEqualToAnchor(view.centerYAnchor).active = true
                leadingAnchor.constraintGreaterThanOrEqualToAnchor(view.leadingAnchor, 20.0 as CGFloat).active = true
                trailingAnchor.constraintLessThanOrEqualToAnchor(view.trailingAnchor, -20.0 as CGFloat).active = true
            }

            datePickerView = UIDatePicker().apply {
                calendar = NSCalendar.currentCalendar.apply {
                    this.locale = datePicker.locale.nsLocale
                    this.timeZone = datePicker.selectedDate.timeZone.timeZone
                }
                date = NSDate.dateWithTimeIntervalSince1970((datePicker.selectedDate.millisecondSinceEpoch.toDouble() / 1000.0) as NSTimeInterval)
                locale = datePicker.locale.nsLocale
                timeZone = datePicker.selectedDate.timeZone.timeZone
                datePickerMode = when (val type = datePicker.type) {
                    is DateTimePicker.Type.TimeType -> UIDatePickerMode.UIDatePickerModeTime
                    is DateTimePicker.Type.DateType -> {
                        type.latestDate?.let {
                            maximumDate = NSDate.dateWithTimeIntervalSince1970((it.millisecondSinceEpoch.toDouble() / 1000.0) as NSTimeInterval)
                        }
                        type.earliestDate?.let {
                            minimumDate = NSDate.dateWithTimeIntervalSince1970((it.millisecondSinceEpoch.toDouble() / 1000.0) as NSTimeInterval)
                        }
                        UIDatePickerMode.UIDatePickerModeDate
                    }
                }
                when {
                    IOSVersion.systemVersion >= IOSVersion(14, 0, 0) -> {
                        preferredDatePickerStyle = UIDatePickerStyle.UIDatePickerStyleInline
                    }
                    IOSVersion.systemVersion >= IOSVersion(13, 4, 0) -> {
                        preferredDatePickerStyle = UIDatePickerStyle.UIDatePickerStyleWheels
                    }
                }

                containerView.addSubview(this)
                translatesAutoresizingMaskIntoConstraints = false
                leadingAnchor.constraintEqualToAnchor(containerView.leadingAnchor, 10.0 as CGFloat).active = true
                trailingAnchor.constraintEqualToAnchor(containerView.trailingAnchor, -10.0 as CGFloat).active = true
                bottomAnchor.constraintEqualToAnchor(containerView.bottomAnchor, -10.0 as CGFloat).active = true
            }

            val topAnchor = datePicker.message?.let {
                UILabel().apply {
                    text = it
                    containerView.addSubview(this)
                    translatesAutoresizingMaskIntoConstraints = false
                    bottomAnchor.constraintEqualToAnchor(
                        datePickerView.topAnchor,
                        -15.0 as CGFloat
                    ).active = true
                    containerView.leadingAnchor.constraintEqualToAnchor(
                        leadingAnchor,
                        -20.0 as CGFloat
                    ).active = true
                    containerView.trailingAnchor.constraintEqualToAnchor(
                        trailingAnchor,
                        20.0 as CGFloat
                    ).active = true
                    bottomAnchor
                }.topAnchor
            } ?: datePickerView.topAnchor

            topAnchor.constraintEqualToAnchor(containerView.topAnchor, 10.0 as CGFloat).active = true

            UIButton().apply {
                addTarget(this@DateTimePickerViewController, sel_registerName("onSelected"), UIControlEventTouchUpInside)
                view.insertSubview(this, 0)
                translatesAutoresizingMaskIntoConstraints = false
                view.topAnchor.constraintEqualToAnchor(this.topAnchor).active = true
                view.bottomAnchor.constraintEqualToAnchor(bottomAnchor).active = true
                view.leadingAnchor.constraintEqualToAnchor(leadingAnchor).active = true
                view.trailingAnchor.constraintEqualToAnchor(trailingAnchor).active = true
            }
        }

        @ObjCAction
        private fun onSelected() {
            completion(Date.epoch((datePickerView.date.timeIntervalSince1970 * 1000.0).toLong(), datePicker.selectedDate.timeZone, datePicker.locale))
            dismissDateTimePicker(true)
        }
    }

    actual class Builder(private val viewController: UIViewController) : BaseDateTimePickerPresenter.Builder() {
        actual override fun create(coroutineScope: CoroutineScope) =
            DateTimePickerPresenter(createDateTimePicker(), viewController)
    }

    override fun dismissDateTimePicker(animated: Boolean) {
        parent.dismissModalViewControllerAnimated(animated)
    }

    override fun showDateTimePicker(animated: Boolean, completion: (Date?) -> Unit) {
        parent.presentViewController(DateTimePickerViewController(datePicker, completion), animated, null)
    }
}

/*

Copyright 2020 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.datetimepicker

import com.splendo.kaluga.base.utils.Date
import kotlinx.coroutines.CoroutineScope

actual class DateTimePickerPresenter(
    dateTimePicker: DateTimePicker
) : BaseDateTimePickerPresenter(dateTimePicker) {

    actual class Builder : BaseDateTimePickerPresenter.Builder() {

        actual override fun create(coroutineScope: CoroutineScope): DateTimePickerPresenter {
            return DateTimePickerPresenter(createDateTimePicker())
        }
    }

    override fun showAsync(animated: Boolean, completion: (Date?) -> Unit) {
        TODO("Not yet implemented")
    }

    override suspend fun show(animated: Boolean): Date? {
        TODO("not implemented")
    }

    override fun dismiss(animated: Boolean) {
        TODO("not implemented")
    }

    override fun dismissDateTimePicker(animated: Boolean) {
        TODO("not implemented")
    }

    override fun showDateTimePicker(animated: Boolean, completion: (Date?) -> Unit) {
        TODO("not implemented")
    }
}

/*

Copyright 2020 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.datetimepicker

import com.splendo.kaluga.base.utils.Date
import kotlinx.coroutines.CoroutineScope

actual class DateTimePickerPresenter(
    datePicker: DateTimePicker
) : BaseDateTimePickerPresenter(datePicker) {

    actual class Builder : BaseDateTimePickerPresenter.Builder() {

        actual override fun create(coroutineScope: CoroutineScope): DateTimePickerPresenter {
            return DateTimePickerPresenter(createDateTimePicker())
        }
    }

    override fun showAsync(animated: Boolean, completion: (Date?) -> Unit) {
        TODO("Not yet implemented")
    }

    override suspend fun show(animated: Boolean): Date? {
        TODO("not implemented")
    }

    override fun dismiss(animated: Boolean) {
        TODO("not implemented")
    }

    override fun dismissDateTimePicker(animated: Boolean) {
        TODO("not implemented")
    }

    override fun showDateTimePicker(animated: Boolean, completion: (Date?) -> Unit) {
        TODO("not implemented")
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.fields

import com.splendo.kaluga.fields.FieldContent.*

fun parseEmailAddress(email: String, invalid: Reason, empty: Reason? = null): EmailAddress {
    TODO()
}

sealed class EmailAddress(override val content: FieldContent, reason: Reason? = null) : Field(content, reason) {

    fun parse(): EmailAddress {
        TODO()
    }

    class ValidEmailAddress(override val content: FieldContent, reason: Reason? = null)
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.

*/

@file:JvmName("AndroidHUD")
package com.splendo.kaluga.hud

import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Resources.ID_NULL
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.fragment.app.DialogFragment
import com.splendo.kaluga.architecture.lifecycle.LifecycleManagerObserver
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable
import com.splendo.kaluga.architecture.lifecycle.getOrPutAndRemoveOnDestroyFromCache
import com.splendo.kaluga.architecture.lifecycle.lifecycleManagerObserver
import com.splendo.kaluga.base.mainHandler
import com.splendo.kaluga.base.utils.byOrdinalOrDefault
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

actual class HUD private constructor(@LayoutRes viewResId: Int, override val hudConfig: HudConfig, lifecycleManagerObserver: LifecycleManagerObserver, coroutineScope: CoroutineScope) : BaseHUD(coroutineScope) {

    actual class Builder(private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver()) : BaseHUD.Builder(), LifecycleSubscribable by lifecycleManagerObserver {

        actual override fun create(hudConfig: HudConfig, coroutineScope: CoroutineScope) = HUD(
            R.layout.loading_indicator_view,
            hudConfig,
            lifecycleManagerObserver,
            coroutineScope
        )
    }

    internal class LoadingDialog : DialogFragment() {

        var presentCompletionBlock: () -> Unit = {}
        var dismissCompletionBlock: () -> Unit = {}

        private val viewResId get() = arguments?.getInt(RESOURCE_ID_KEY) ?: ID_NULL
        private val style get() = Enum.byOrdinalOrDefault(arguments?.getInt(STYLE_KEY) ?: -1, HUDStyle.SYSTEM)
        private val title get() = arguments?.getString(TITLE_KEY)

        @ColorInt
        private fun backgroundColor(context: Context): Int = when (style) {
            HUDStyle.SYSTEM -> resolveAttrColor(context, android.R.attr.windowBackground)
            HUDStyle.CUSTOM -> ContextCompat.getColor(context, R.color.li_colorBackground)
        }

        @ColorInt
        private fun foregroundColor(context: Context): Int = when (style) {
            HUDStyle.SYSTEM -> resolveAttrColor(context, android.R.attr.colorAccent)
            HUDStyle.CUSTOM -> ContextCompat.getColor(context, R.color.li_colorAccent)
        }

        @ColorInt
        private fun resolveAttrColor(context: Context, @AttrRes colorAttrRes: Int): Int {
            val typedValue = TypedValue()
            context.theme.resolveAttribute(colorAttrRes, typedValue, true)
            return typedValue.data
        }

        companion object {

            private const val RESOURCE_ID_KEY = "resId"
            private const val STYLE_KEY = "style"
            private const val TITLE_KEY = "title"

            fun newInstance(@LayoutRes viewResId: Int, hudConfig: HudConfig) = LoadingDialog().apply {
                arguments = Bundle().apply {
                    putInt(RESOURCE_ID_KEY, viewResId)
                    putInt(STYLE_KEY, hudConfig.style.ordinal)
                    putString(TITLE_KEY, hudConfig.title)
                }
                isCancelable = false
            }
        }

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // Dismiss existing dialog on rotation
            // We will handle this state via dialogState and show it again if needed
            if (savedInstanceState != null) {
                dismiss()
            }
        }

        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
            return inflater.inflate(viewResId, container).apply {
                findViewById<LinearLayout>(R.id.content_view).setBackgroundColor(backgroundColor(inflater.context))
                findViewById<ProgressBar>(R.id.progress_bar).indeterminateTintList = ColorStateList.valueOf(foregroundColor(inflater.context))
                findViewById<TextView>(R.id.text_view).apply {
                    text = title
                    visibility = if (title == null) View.GONE else View.VISIBLE
                }
            }
        }

        override fun onResume() {
            super.onResume()
            mainHandler.post {
                presentCompletionBlock()
                presentCompletionBlock = {}
            }
        }

        override fun onPause() {
            super.onPause()
            mainHandler.post {
                dismissCompletionBlock()
                dismissCompletionBlock = {}
            }
        }
    }

    private sealed class DialogState {
        object Gone : DialogState()
        object Visible : DialogState()
    }

    private val loadingDialog = LoadingDialog.newInstance(viewResId, hudConfig)
    private var dialogState = MutableStateFlow<DialogState>(DialogState.Gone)

    init {
        launch {
            combine(lifecycleManagerObserver.managerState, dialogState) { uiContextData, dialogPresentation ->
                Pair(uiContextData, dialogPresentation)
            }.collect { contextPresentation ->
                when (contextPresentation.second) {
                    is DialogState.Visible -> contextPresentation.first?.fragmentManager?.let { loadingDialog.show(it, "Kaluga.HUD") }
                    is DialogState.Gone -> if (loadingDialog.isAdded) loadingDialog.dismiss()
                }
            }
        }
    }

    override val isVisible get() = loadingDialog.isVisible

    override suspend fun present(animated: Boolean): HUD {
        return suspendCoroutine { continuation ->
            loadingDialog.presentCompletionBlock = {
                continuation.resume(this)
            }
            dialogState.value = DialogState.Visible
        }
    }

    override suspend fun dismiss(animated: Boolean) {
        suspendCoroutine<Unit> { continuation ->
            loadingDialog.dismissCompletionBlock = {
                continuation.resume(Unit)
            }

            dialogState.value = DialogState.Gone
        }
    }
}

/**
 * @return A [HUD.Builder] which can be used to show an HUD while this Activity is active.
 *  Will be created if need but only one instance will exist.
 *
 * Warning: Do not attempt to use this builder outside of the lifespan of the Activity.
 * Instead, for example use a [com.splendo.kaluga.architecture.viewmodel.ViewModel],
 * which can automatically track which Activity is active for it.
 *
 */
fun AppCompatActivity.hudBuilder(): HUD.Builder = getOrPutAndRemoveOnDestroyFromCache {
    HUD.Builder(lifecycleManagerObserver())
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.hud

import co.touchlab.stately.concurrency.Lock
import co.touchlab.stately.concurrency.withLock
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

/**
 * Style of the Loading Indicator
 */
enum class HUDStyle {
    /** System appearance */
    SYSTEM,
    /** Custom appearance */
    CUSTOM
}

/**
 * Class showing a loading indicator HUD.
 */
abstract class BaseHUD(coroutineScope: CoroutineScope) : CoroutineScope by coroutineScope {

    /**
     * Builder class for creating a [BaseHUD]
     */
    abstract class Builder : LifecycleSubscribableMarker {

        internal val lock = Lock()

        /** The style of the loading indicator */
        internal var style: HUDStyle = HUDStyle.SYSTEM

        /** Sets the style for the loading indicator */
        fun setStyle(style: HUDStyle) = apply { this.style = style }

        /** The title of the loading indicator */
        internal var title: String? = null

        /** Set the title for the loading indicator */
        fun setTitle(title: String?) = apply { this.title = title }

        /** Sets default style and empty title */
        internal fun clear() {
            setStyle(HUDStyle.SYSTEM)
            setTitle(null)
        }

        /** */
        /**
         * Builds a [BaseHUD] based on some [HudConfig].
         *
         * @param hudConfig The [HudConfig] used for configuring the HUD style.
         * @param coroutineScope The [CoroutineScope] managing the HUD lifecycle.
         * @return The [BaseHUD] to diplay.
         */
        abstract fun create(hudConfig: HudConfig, coroutineScope: CoroutineScope): BaseHUD
    }

    abstract val hudConfig: HudConfig

    /**
     * Returns true if indicator is visible
     */
    abstract val isVisible: Boolean

    /**
     * Presents as indicator
     *
     * @param animated Pass `true` to animate the presentation
     */
    abstract suspend fun present(animated: Boolean = true): BaseHUD

    /**
     * Dismisses the indicator
     *
     * @param animated Pass `true` to animate the transition
     */
    abstract suspend fun dismiss(animated: Boolean = true)
}

/**
 * Default [BaseHUD] implementation.
 */
expect class HUD : BaseHUD {

    /**
     * Builder class for creating a [HUD]
     */
    class Builder : BaseHUD.Builder {
        override fun create(hudConfig: HudConfig, coroutineScope: CoroutineScope): HUD
    }
}

val BaseHUD.title: String? get() = hudConfig.title
val BaseHUD.style: HUDStyle get() = hudConfig.style

/**
 * Dismisses the indicator after [timeMillis] milliseconds
 * @param timeMillis The number of milliseconds to wait
 */
fun BaseHUD.dismissAfter(timeMillis: Long, animated: Boolean = true): BaseHUD = apply {
    launch(Dispatchers.Main) {
        delay(timeMillis)
        dismiss(animated)
    }
}

/**
 * Presents and keep presenting the indicator during block execution,
 * hides view after block finished.
 * @param block The block to execute with hud visible
 */
suspend fun <T> BaseHUD.presentDuring(animated: Boolean = true, block: suspend BaseHUD.() -> T): T {
    present(animated)
    return block().also { dismiss(animated) }
}

/**
 * Builds a [HUD] to display a loading indicator
 *
 * @param coroutineScope The [CoroutineScope] managing the HUD lifecycle.
 * @param initialize Method for initializing the [HUD.Builder]
 */
fun BaseHUD.Builder.build(coroutineScope: CoroutineScope, initialize: BaseHUD.Builder.() -> Unit = { }): BaseHUD = lock.withLock {
    clear()
    initialize()
    return create(HudConfig(style, title), coroutineScope)
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.hud

data class HudConfig(
    /** Style of the HUD */
    val style: HUDStyle = HUDStyle.SYSTEM,
    /** Optional title of the HUD */
    val title: String? = null
)

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.

*/

@file:Suppress("USELESS_CAST") // as CGFloat is needed for arm32.

package com.splendo.kaluga.hud

import kotlinx.cinterop.CValue
import kotlinx.coroutines.CoroutineScope
import platform.CoreGraphics.CGFloat
import platform.CoreGraphics.CGRect
import platform.UIKit.NSLayoutConstraint
import platform.UIKit.UIActivityIndicatorView
import platform.UIKit.UIActivityIndicatorViewStyleWhiteLarge
import platform.UIKit.UIColor
import platform.UIKit.UILabel
import platform.UIKit.UILayoutConstraintAxisVertical
import platform.UIKit.UIModalPresentationOverFullScreen
import platform.UIKit.UIModalTransitionStyleCrossDissolve
import platform.UIKit.UIScreen
import platform.UIKit.UIStackView
import platform.UIKit.UIStackViewAlignmentCenter
import platform.UIKit.UIStackViewDistributionFill
import platform.UIKit.UIUserInterfaceStyle
import platform.UIKit.UIView
import platform.UIKit.UIViewAutoresizingFlexibleHeight
import platform.UIKit.UIViewAutoresizingFlexibleWidth
import platform.UIKit.UIViewController
import platform.UIKit.addSubview
import platform.UIKit.autoresizingMask
import platform.UIKit.backgroundColor
import platform.UIKit.bottomAnchor
import platform.UIKit.centerXAnchor
import platform.UIKit.centerYAnchor
import platform.UIKit.colorNamed
import platform.UIKit.heightAnchor
import platform.UIKit.hidden
import platform.UIKit.leadingAnchor
import platform.UIKit.setBackgroundColor
import platform.UIKit.topAnchor
import platform.UIKit.trailingAnchor
import platform.UIKit.translatesAutoresizingMaskIntoConstraints
import platform.UIKit.widthAnchor
import platform.UIKit.window
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

actual class HUD private constructor(private val containerView: ContainerView, private val viewController: UIViewController, wrapper: (UIViewController) -> UIViewController, coroutineScope: CoroutineScope) : BaseHUD(coroutineScope) {

    actual class Builder(private val viewController: UIViewController, private val wrapper: (UIViewController) -> UIViewController) : BaseHUD.Builder() {

        constructor(viewController: UIViewController) : this(viewController, { it })

        actual override fun create(hudConfig: HudConfig, coroutineScope: CoroutineScope) = HUD(
            ContainerView(hudConfig, viewController.view.window?.bounds ?: UIScreen.mainScreen.bounds),
            viewController,
            wrapper,
            coroutineScope
        )
    }

    private class ContainerView(
        internal val hudConfig: HudConfig,
        frame: CValue<CGRect>
    ) : UIView(frame) {

        private val titleLabel: UILabel

        private val backgroundColor: UIColor
            get() = when (hudConfig.style) {
                HUDStyle.CUSTOM -> UIColor.colorNamed("li_colorBackground") ?: UIColor.lightGrayColor
                HUDStyle.SYSTEM ->
                    if (traitCollection.userInterfaceStyle == UIUserInterfaceStyle.UIUserInterfaceStyleDark)
                        UIColor.blackColor else UIColor.whiteColor
            }

        private val foregroundColor: UIColor
            get() = when (hudConfig.style) {
                HUDStyle.CUSTOM -> UIColor.colorNamed("li_colorAccent") ?: UIColor.darkGrayColor
                HUDStyle.SYSTEM ->
                    if (traitCollection.userInterfaceStyle == UIUserInterfaceStyle.UIUserInterfaceStyleDark)
                        UIColor.whiteColor else UIColor.blackColor
            }

        // NOTES: Cast to CGFloat is needed for Arm32
        init {
            // Rotation support
            autoresizingMask = UIViewAutoresizingFlexibleWidth or UIViewAutoresizingFlexibleHeight
            // Dimmed background
            setBackgroundColor(UIColor(0.0 as CGFloat, (1 / 3.0) as CGFloat))
            // Main HUD view is stack view
            val stackView = UIStackView().apply {
                axis = UILayoutConstraintAxisVertical
                alignment = UIStackViewAlignmentCenter
                distribution = UIStackViewDistributionFill
                spacing = 8.0 as CGFloat
                translatesAutoresizingMaskIntoConstraints = false
            }
            addSubview(stackView)

            // Stack view background view
            val contentView = UIView().apply {
                backgroundColor = this@ContainerView.backgroundColor
                layer.cornerRadius = 14.0 as CGFloat
                translatesAutoresizingMaskIntoConstraints = false
            }
            stackView.addSubview(contentView)
            NSLayoutConstraint.activateConstraints(
                listOf(
                    contentView.leadingAnchor.constraintEqualToAnchor(stackView.leadingAnchor, -32.0 as CGFloat),
                    contentView.trailingAnchor.constraintEqualToAnchor(stackView.trailingAnchor, 32.0 as CGFloat),
                    contentView.topAnchor.constraintEqualToAnchor(stackView.topAnchor, -32.0 as CGFloat),
                    contentView.bottomAnchor.constraintEqualToAnchor(stackView.bottomAnchor, 32.0 as CGFloat),
                    stackView.centerXAnchor.constraintEqualToAnchor(centerXAnchor),
                    stackView.centerYAnchor.constraintEqualToAnchor(centerYAnchor),
                    stackView.widthAnchor.constraintGreaterThanOrEqualToConstant(64.0 as CGFloat),
                    stackView.widthAnchor.constraintLessThanOrEqualToAnchor(widthAnchor, 0.5 as CGFloat),
                    stackView.heightAnchor.constraintGreaterThanOrEqualToConstant(64.0 as CGFloat),
                    stackView.heightAnchor.constraintLessThanOrEqualToAnchor(heightAnchor, 0.5 as CGFloat)
                )
            )
            // Activity indicator
            UIActivityIndicatorView(UIActivityIndicatorViewStyleWhiteLarge).apply {
                color = foregroundColor
                startAnimating()
            }.also {
                stackView.addArrangedSubview(it)
            }
            // Place title label
            titleLabel = UILabel().apply {
                text = hudConfig.title
                hidden = hudConfig.title?.isEmpty() ?: true
                numberOfLines = 0 // Multiline support
            }.also {
                stackView.addArrangedSubview(it)
            }
        }
    }

    private val hudViewController = wrapper(
        UIViewController(null, null).apply {
            modalPresentationStyle = UIModalPresentationOverFullScreen
            modalTransitionStyle = UIModalTransitionStyleCrossDissolve
            view.backgroundColor = UIColor.clearColor
            view.addSubview(containerView)
        }
    )

    private val topViewController: UIViewController
        get() {
            var result: UIViewController? = viewController
            while (result?.presentedViewController != null) {
                result = result.presentedViewController
            }
            return result ?: viewController
        }

    override val hudConfig: HudConfig = containerView.hudConfig
    override val isVisible: Boolean get() = hudViewController.presentingViewController() != null

    override suspend fun present(animated: Boolean): HUD = suspendCoroutine { continuation ->
        if (!isVisible) {
            topViewController.presentViewController(hudViewController, animated) {
                continuation.resume(this)
            }
        } else {
            continuation.resume(this)
        }
    }

    override suspend fun dismiss(animated: Boolean) = suspendCoroutine<Unit> { continuation ->
        if (isVisible) {
            hudViewController.presentingViewController?.dismissViewControllerAnimated(animated) {
                continuation.resume(Unit)
            }
        } else {
            continuation.resume(Unit)
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.hud

import kotlinx.coroutines.CoroutineScope

actual class HUD(override val hudConfig: HudConfig, coroutineScope: CoroutineScope) : BaseHUD(coroutineScope) {
    actual class Builder : BaseHUD.Builder() {
        actual override fun create(hudConfig: HudConfig, coroutineScope: CoroutineScope) = HUD(hudConfig, coroutineScope)
    }

    private var _isVisible: Boolean = false
    override val isVisible: Boolean get() = _isVisible

    override suspend fun present(animated: Boolean): HUD = apply {
        _isVisible = true
    }

    override suspend fun dismiss(animated: Boolean) {
        _isVisible = false
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.hud

import kotlinx.coroutines.CoroutineScope

actual class HUD(override val hudConfig: HudConfig, coroutineScope: CoroutineScope) : BaseHUD(coroutineScope) {
    actual class Builder : BaseHUD.Builder() {
        actual override fun create(hudConfig: HudConfig, coroutineScope: CoroutineScope) = HUD(hudConfig, coroutineScope)
    }

    private var _isVisible: Boolean = false
    override val isVisible: Boolean get() = _isVisible

    override suspend fun present(animated: Boolean): HUD = apply {
        _isVisible = true
    }

    override suspend fun dismiss(animated: Boolean) {
        _isVisible = false
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.keyboard

import android.app.Activity
import android.view.View
import androidx.annotation.IdRes
import androidx.compose.ui.focus.FocusRequester

actual interface FocusHandler {
    fun requestFocus(activity: Activity?)
}

class ComposeFocusHandler(private val focusRequester: FocusRequester) : FocusHandler {
    override fun requestFocus(activity: Activity?) {
        focusRequester.requestFocus()
    }
}

class AndroidFocusHandler(
    @IdRes private val id: Int
) : FocusHandler {
    override fun requestFocus(activity: Activity?) {
        val view = activity?.findViewById<View>(id)
        view?.requestFocus()
    }
}

/*
Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.keyboard

import android.app.Activity
import android.content.Context.INPUT_METHOD_SERVICE
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AppCompatActivity
import com.splendo.kaluga.architecture.lifecycle.LifecycleManagerObserver
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable
import com.splendo.kaluga.architecture.lifecycle.getOrPutAndRemoveOnDestroyFromCache
import com.splendo.kaluga.architecture.lifecycle.lifecycleManagerObserver
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch

actual class KeyboardManager(
    private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver(),
    coroutineScope: CoroutineScope
) : BaseKeyboardManager, CoroutineScope by coroutineScope {

    actual class Builder(
        private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver()
    ) : BaseKeyboardManager.Builder, LifecycleSubscribable by lifecycleManagerObserver {
        actual override fun create(coroutineScope: CoroutineScope) = KeyboardManager(lifecycleManagerObserver, coroutineScope)
    }

    init {
        launch {
            lifecycleManagerObserver.managerState.collect {
                activity = it?.activity
            }
        }
    }

    private var activity: Activity? = null
        set(value) {
            field = value
            inputMethodManager = value?.getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager
        }
    private var inputMethodManager: InputMethodManager? = null

    override fun show(focusHandler: FocusHandler) {
        inputMethodManager?.let { inputManager ->
            activity?.let {
                focusHandler.requestFocus(it)
                inputManager.toggleSoftInput(
                    InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY
                )
            }
        }
    }

    override fun hide() {
        inputMethodManager?.let {
            if (it.isAcceptingText) {
                it.hideSoftInputFromWindow(activity?.currentFocus?.windowToken, 0)
            }
        }
    }
}

/**
 * @return A [KeyboardManager.Builder] which can be used to manipulate the soft keyboard while this Activity is active.
 * Will be created if need but only one instance will exist.
 *
 * Warning: Do not attempt to use this builder outside of the lifespan of the Activity.
 * Instead, for example use a [com.splendo.kaluga.architecture.viewmodel.ViewModel],
 * which can automatically track which Activity is active for it.
 *
 */
fun AppCompatActivity.keyboardManagerBuilder(): KeyboardManager.Builder = getOrPutAndRemoveOnDestroyFromCache {
    KeyboardManager.Builder(lifecycleManagerObserver())
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.keyboard

import kotlinx.coroutines.CoroutineScope

/**
 * Common interface which handles the focus on a view.
 */
expect interface FocusHandler

/**
 * Interface that defines the actions available for the Keyboard Manager
 */
interface BaseKeyboardManager {

    /**
     * Base KeyboardManager builder class, which used to create an KeyboardManager
     *
     * @see KeyboardManager
     */
    interface Builder {

        /**
         * Creates KeyboardManager object
         *
         * @return The KeyboardManager object
         */
        fun create(coroutineScope: CoroutineScope): BaseKeyboardManager
    }

    /**
     * Shows the keyboard for a given [KeyboardHostingView]
     *
     * @param keyboardHostingView The view for which the keyboard will be shown
     */
    fun show(focusHandler: FocusHandler)

    /**
     * Dismisses the current keyboard
     */
    fun hide()
}

/**
 * Manager for Showing and Hiding the Keyboard.
 */
expect class KeyboardManager : BaseKeyboardManager {

    /**
     * Builder for creating a [KeyboardManager].
     */
    class Builder : BaseKeyboardManager.Builder {
        override fun create(coroutineScope: CoroutineScope): KeyboardManager
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.keyboard

import platform.UIKit.UIView

actual interface FocusHandler {
    fun requestFocus()
}

class UIKitFocusHandler(val view: UIView) : FocusHandler {
    override fun requestFocus() {
        if (view.canBecomeFirstResponder) {
            view.becomeFirstResponder()
        }
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.keyboard

import kotlinx.coroutines.CoroutineScope
import platform.UIKit.UIApplication
import platform.darwin.sel_registerName

actual class KeyboardManager(private val application: UIApplication) : BaseKeyboardManager {

    actual class Builder(private val application: UIApplication = UIApplication.sharedApplication) : BaseKeyboardManager.Builder {
        actual override fun create(coroutineScope: CoroutineScope) = KeyboardManager(application)
    }

    override fun show(focusHandler: FocusHandler) {
        focusHandler.requestFocus()
    }

    override fun hide() {
        application.sendAction(sel_registerName("resignFirstResponder"), null, null, null)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.keyboard

actual interface FocusHandler

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.keyboard

import kotlinx.coroutines.CoroutineScope

actual class KeyboardManager : BaseKeyboardManager {

    actual class Builder : BaseKeyboardManager.Builder {
        actual override fun create(coroutineScope: CoroutineScope) = KeyboardManager()
    }

    override fun show(focusHandler: FocusHandler) {
        TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
    }

    override fun hide() {
        TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.keyboard

actual interface FocusHandler

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.keyboard

import kotlinx.coroutines.CoroutineScope

actual class KeyboardManager : BaseKeyboardManager {

    actual class Builder : BaseKeyboardManager.Builder {
        actual override fun create(coroutineScope: CoroutineScope) = KeyboardManager()
    }

    override fun show(focusHandler: FocusHandler) {
        TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
    }

    override fun hide() {
        TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.location

import android.Manifest
import android.content.Context
import com.splendo.kaluga.permissions.AndroidPermissionsManager
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class LocationPermissionManager(
    context: Context,
    actual val location: LocationPermission,
    stateRepo: LocationPermissionStateRepo
) : PermissionManager<LocationPermission>(stateRepo) {

    private val permissions: Array<String> get() {
        val result = mutableListOf(Manifest.permission.ACCESS_COARSE_LOCATION)
        if (location.precise)
            result.add(Manifest.permission.ACCESS_FINE_LOCATION)
        if (location.background && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q)
            result.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
        return result.toTypedArray()
    }

    private val permissionsManager = AndroidPermissionsManager(context, this, permissions)

    override suspend fun requestPermission() {
        permissionsManager.requestPermissions()
    }

    override suspend fun initializeState(): PermissionState<LocationPermission> {
        return when {
            permissionsManager.hasPermissions -> PermissionState.Allowed()
            else -> PermissionState.Denied.Requestable()
        }
    }

    override suspend fun startMonitoring(interval: Long) {
        permissionsManager.startMonitoring(interval)
    }

    override suspend fun stopMonitoring() {
        permissionsManager.stopMonitoring()
    }
}

actual class LocationPermissionManagerBuilder actual constructor(private val context: PermissionContext) : BaseLocationPermissionManagerBuilder {

    override fun create(location: LocationPermission, repo: LocationPermissionStateRepo): PermissionManager<LocationPermission> {
        return LocationPermissionManager(context.context, location, repo)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.location

import com.splendo.kaluga.permissions.BasePermissionsBuilder
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionStateRepo
import com.splendo.kaluga.permissions.defaultPermissionContext
import kotlin.coroutines.CoroutineContext

/**
 * A [PermissionManager] for managing [LocationPermission]
 */
expect class LocationPermissionManager : PermissionManager<LocationPermission> {
    /**
     * The [LocationPermission] managed by this manager.
     */
    val location: LocationPermission
}

interface BaseLocationPermissionManagerBuilder : BasePermissionsBuilder {

    /**
     * Creates a [LocationPermissionManager]
     * @param repo The [LocationPermissionStateRepo] associated with the [LocationPermission]
     */
    fun create(location: LocationPermission, repo: LocationPermissionStateRepo): PermissionManager<LocationPermission>
}

/**
 * A builder for creating a [LocationPermissionManager]
 */
expect class LocationPermissionManagerBuilder(context: PermissionContext = defaultPermissionContext) : BaseLocationPermissionManagerBuilder

/**
 * A [PermissionStateRepo] for [LocationPermission]
 * @param builder The [LocationPermissionManagerBuilder] for creating the [LocationPermissionManager] associated with the permission
 * @param coroutineContext The [CoroutineContext] to run the state machine on.
 */
class DefaultLocationPermissionStateRepo(
    location: LocationPermission,
    builder: BaseLocationPermissionManagerBuilder,
    coroutineContext: CoroutineContext
) : LocationPermissionStateRepo(coroutineContext = coroutineContext) {
    override val permissionManager: PermissionManager<LocationPermission> =
        builder.create(location, this)
}

abstract class LocationPermissionStateRepo(coroutineContext: CoroutineContext) : PermissionStateRepo<LocationPermission>(coroutineContext = coroutineContext)

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.location

import com.splendo.kaluga.permissions.Permission
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.defaultPermissionContext

/**
 * Permission to access the users Location
 * @param background If `true` scanning for location in the background is permitted
 * @param precise If `true` precise location scanning is permitted
 */
data class LocationPermission(val background: Boolean = false, val precise: Boolean = false) : Permission()

fun PermissionsBuilder.registerLocationPermission() =
    registerLocationPermissionBuilder(context).also { builder ->
        registerPermissionStateRepoBuilder(LocationPermission::class) { permission, coroutineContext ->
            DefaultLocationPermissionStateRepo(permission as LocationPermission, builder as BaseLocationPermissionManagerBuilder, coroutineContext)
        }
    }

internal fun PermissionsBuilder.registerLocationPermissionBuilder(context: PermissionContext = defaultPermissionContext): LocationPermissionManagerBuilder = register(
    builder = LocationPermissionManagerBuilder(context),
    permission = LocationPermission::class
)

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.location

import com.splendo.kaluga.base.utils.byOrdinalOrDefault
import com.splendo.kaluga.permissions.IOSPermissionsHelper
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState
import platform.CoreLocation.CLAuthorizationStatus
import platform.CoreLocation.CLLocationManager
import platform.CoreLocation.CLLocationManagerDelegateProtocol
import platform.Foundation.NSBundle
import platform.darwin.NSObject

const val NSLocationWhenInUseUsageDescription = "NSLocationWhenInUseUsageDescription"
const val NSLocationAlwaysAndWhenInUseUsageDescription = "NSLocationAlwaysAndWhenInUseUsageDescription"
const val NSLocationAlwaysUsageDescription = "NSLocationAlwaysUsageDescription"

actual class LocationPermissionManager(
    private val bundle: NSBundle,
    actual val location: LocationPermission,
    stateRepo: LocationPermissionStateRepo
) : PermissionManager<LocationPermission>(stateRepo) {

    private val locationManager = CLLocationManager()
    private val authorizationStatus = {
        CLLocationManager.authorizationStatus().toCLAuthorizationStatusKotlin().toAuthorizationStatus(location.background)
    }

    private val delegate = object : NSObject(), CLLocationManagerDelegateProtocol {

        override fun locationManager(manager: CLLocationManager, didChangeAuthorizationStatus: CLAuthorizationStatus /* = kotlin.Int */) {
            val locationPermissionManager = this@LocationPermissionManager
            IOSPermissionsHelper.handleAuthorizationStatus(didChangeAuthorizationStatus.toCLAuthorizationStatusKotlin().toAuthorizationStatus(locationPermissionManager.location.background), locationPermissionManager)
        }
    }

    override suspend fun requestPermission() {
        val locationDeclarations = mutableListOf(NSLocationWhenInUseUsageDescription)
        if (location.background) {
            locationDeclarations.addAll(listOf(NSLocationAlwaysAndWhenInUseUsageDescription, NSLocationAlwaysUsageDescription))
        }
        if (IOSPermissionsHelper.missingDeclarationsInPList(bundle, *locationDeclarations.toTypedArray()).isEmpty()) {
            if (location.background)
                locationManager.requestAlwaysAuthorization()
            else
                locationManager.requestWhenInUseAuthorization()
        } else {
            revokePermission(true)
        }
    }

    override suspend fun initializeState(): PermissionState<LocationPermission> {
        return IOSPermissionsHelper.getPermissionState(authorizationStatus())
    }

    override suspend fun startMonitoring(interval: Long) {
        locationManager.delegate = delegate
    }

    override suspend fun stopMonitoring() {
        locationManager.delegate = null
    }
}

actual class LocationPermissionManagerBuilder actual constructor(private val context: PermissionContext) : BaseLocationPermissionManagerBuilder {

    override fun create(location: LocationPermission, repo: LocationPermissionStateRepo): PermissionManager<LocationPermission> {
        return LocationPermissionManager(context, location, repo)
    }
}

@Suppress("EnumEntryName") // we are modeling an iOS construct so we will stick as close to it as possible. Actual CLAuthorizationStatus values not available
enum class CLAuthorizationStatusKotlin {
    // https://developer.apple.com/documentation/corelocation/clauthorizationstatus
    notDetermined,
    restricted,
    denied,
    authorizedAlways,
    authorizedWhenInUse
}

fun CLAuthorizationStatus.toCLAuthorizationStatusKotlin(): CLAuthorizationStatusKotlin {
    return Enum.byOrdinalOrDefault(
        this,
        CLAuthorizationStatusKotlin.notDetermined
    )
}

private fun CLAuthorizationStatusKotlin.toAuthorizationStatus(background: Boolean): IOSPermissionsHelper.AuthorizationStatus {
    return when (this) {
        CLAuthorizationStatusKotlin.notDetermined -> IOSPermissionsHelper.AuthorizationStatus.NotDetermined
        CLAuthorizationStatusKotlin.restricted -> IOSPermissionsHelper.AuthorizationStatus.Restricted
        CLAuthorizationStatusKotlin.denied -> IOSPermissionsHelper.AuthorizationStatus.Denied
        CLAuthorizationStatusKotlin.authorizedAlways -> IOSPermissionsHelper.AuthorizationStatus.Authorized
        CLAuthorizationStatusKotlin.authorizedWhenInUse -> if (background) IOSPermissionsHelper.AuthorizationStatus.Denied else IOSPermissionsHelper.AuthorizationStatus.Authorized
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.location

import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class LocationPermissionManager(actual val location: LocationPermission, repo: LocationPermissionStateRepo) : PermissionManager<LocationPermission>(repo) {

    override suspend fun requestPermission() {
        TODO("not implemented")
    }

    override suspend fun initializeState(): PermissionState<LocationPermission> {
        TODO("not implemented")
    }

    override suspend fun startMonitoring(interval: Long) {
        TODO("not implemented")
    }

    override suspend fun stopMonitoring() {
        TODO("not implemented")
    }
}

actual class LocationPermissionManagerBuilder actual constructor(context: PermissionContext) : BaseLocationPermissionManagerBuilder {

    override fun create(location: LocationPermission, repo: LocationPermissionStateRepo): PermissionManager<LocationPermission> {
        return LocationPermissionManager(location, repo)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.location

import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.location.LocationPermission
import com.splendo.kaluga.permissions.location.registerLocationPermission
import kotlinx.coroutines.Dispatchers
import kotlin.coroutines.CoroutineContext

actual class LocationManager(
    locationPermission: LocationPermission,
    permissions: Permissions,
    autoRequestPermission: Boolean,
    autoEnableLocations: Boolean,
    locationStateRepo: LocationStateRepo
) : BaseLocationManager(locationPermission, permissions, autoRequestPermission, autoEnableLocations, locationStateRepo) {

    class Builder : BaseLocationManager.Builder {
        override fun create(
            locationPermission: LocationPermission,
            permissions: Permissions,
            autoRequestPermission: Boolean,
            autoEnableLocations: Boolean,
            locationStateRepo: LocationStateRepo
        ): BaseLocationManager {
            return LocationManager(locationPermission, permissions, autoRequestPermission, autoEnableLocations, locationStateRepo)
        }
    }

    override val locationMonitor: LocationMonitor = LocationMonitor.Builder().create()

    override suspend fun requestLocationEnable() {
        TODO("not implemented")
    }

    override suspend fun startMonitoringLocation() {
        TODO("not implemented")
    }

    override suspend fun stopMonitoringLocation() {
        TODO("not implemented")
    }
}

actual class LocationStateRepoBuilder(
    private val permissions: Permissions = Permissions(
        PermissionsBuilder().apply { registerLocationPermission() },
        Dispatchers.Main
    )
) : LocationStateRepo.Builder {

    override fun create(locationPermission: LocationPermission, autoRequestPermission: Boolean, autoEnableLocations: Boolean, coroutineContext: CoroutineContext): LocationStateRepo {
        return LocationStateRepo(locationPermission, permissions, autoRequestPermission, autoEnableLocations, LocationManager.Builder(), coroutineContext)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.location

import com.splendo.kaluga.base.DefaultServiceMonitor
import com.splendo.kaluga.base.ServiceMonitor

actual interface LocationMonitor : ServiceMonitor {

    actual class Builder {
        actual fun create(): LocationMonitor = DefaultLocationMonitor()
    }
}

class DefaultLocationMonitor : DefaultServiceMonitor(), LocationMonitor {
    override val isServiceEnabled: Boolean
        get() = TODO("Not yet implemented")

    override fun startMonitoring() = TODO("Not yet implemented")
    override fun stopMonitoring() = TODO("Not yet implemented")
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.location

import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.location.LocationPermission
import com.splendo.kaluga.permissions.location.registerLocationPermission
import kotlinx.coroutines.Dispatchers
import kotlin.coroutines.CoroutineContext

actual class LocationManager(
    locationPermission: LocationPermission,
    permissions: Permissions,
    autoRequestPermission: Boolean,
    autoEnableLocations: Boolean,
    locationStateRepo: LocationStateRepo
) : BaseLocationManager(locationPermission, permissions, autoRequestPermission, autoEnableLocations, locationStateRepo) {

    class Builder : BaseLocationManager.Builder {

        override fun create(
            locationPermission: LocationPermission,
            permissions: Permissions,
            autoRequestPermission: Boolean,
            autoEnableLocations: Boolean,
            locationStateRepo: LocationStateRepo
        ): BaseLocationManager {
            return LocationManager(locationPermission, permissions, autoRequestPermission, autoEnableLocations, locationStateRepo)
        }
    }

    override val locationMonitor: LocationMonitor = LocationMonitor.Builder().create()

    override suspend fun requestLocationEnable() {
        TODO("not implemented")
    }

    override suspend fun startMonitoringLocation() {
        TODO("not implemented")
    }

    override suspend fun stopMonitoringLocation() {
        TODO("not implemented")
    }
}

actual class LocationStateRepoBuilder(
    private val permissions: Permissions = Permissions(
        PermissionsBuilder().apply {
            registerLocationPermission()
        },
        Dispatchers.Main
    )
) : LocationStateRepo.Builder {

    override fun create(locationPermission: LocationPermission, autoRequestPermission: Boolean, autoEnableLocations: Boolean, coroutineContext: CoroutineContext): LocationStateRepo {
        return LocationStateRepo(locationPermission, permissions, autoRequestPermission, autoEnableLocations, LocationManager.Builder(), coroutineContext)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.location

import com.splendo.kaluga.base.DefaultServiceMonitor
import com.splendo.kaluga.base.ServiceMonitor

actual interface LocationMonitor : ServiceMonitor {

    actual class Builder {
        actual fun create(): LocationMonitor = DefaultLocationMonitor()
    }
}

class DefaultLocationMonitor : DefaultServiceMonitor(), LocationMonitor {
    override val isServiceEnabled: Boolean
        get() = TODO("Not yet implemented")

    override fun startMonitoring() = TODO("Not yet implemented")
    override fun stopMonitoring() = TODO("Not yet implemented")
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.

 */

@file:JvmName("AndroidLogKT")
package com.splendo.kaluga.logging

import android.os.Build
import ru.pocketbyte.kydra.log.AndroidLogger
import java.lang.Integer.min

actual val defaultLogger: Logger =
    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M)
        TransformLogger(
            KydraLogger(AndroidLogger()),
            transformTag = { tag ->
                tag?.let {
                    val maxSize = min(23, tag.length - 1)
                    if (maxSize >= 1) {
                        tag.substring(0..maxSize)
                    } else {
                        tag
                    }
                }
            }
        )
    else
        KydraLogger(AndroidLogger())

actual var logger: Logger = defaultLogger

package com.splendo.kaluga.logging
/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.

*/

enum class LogLevel {
    /**
     * Debug Log Level. Should be shown only for debugging.
     */
    DEBUG,

    /**
     * Information Log Level.
     */
    INFO,

    /**
     * Warning Log Level. Should be used to log some warnings.
     */
    WARN,

    /**
     * Error Log Level. Should be used to log some errors.
     */
    ERROR,
}

package com.splendo.kaluga.logging
/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.

 */

interface Logger {

    /**
     * Writes log with provided level and tag.
     * @param level Log level
     * @param tag Tag of the log record. Nullable
     * @param throwable Throwable to be written into log
     * @param message Message to be written into log
     */
    fun log(level: LogLevel, tag: String? = null, throwable: Throwable? = null, message: (() -> String)? = null)
}

open class TransformLogger(
    private val logger: Logger,
    private val transformLogLevel: ((LogLevel) -> LogLevel)? = null,
    private val transformTag: ((String?) -> String?)? = null,
    private val transformThrowable: ((Throwable?) -> Throwable?)? = null,
    private val transformMessage: ((String?) -> String?)? = null,
) : Logger {

    override fun log(level: LogLevel, tag: String?, throwable: Throwable?, message: (() -> String)?) {

        logger.log(
            transformLogLevel?.invoke(level) ?: level,
            transformTag?.invoke(tag) ?: tag,
            transformThrowable?.invoke(throwable) ?: throwable,
            // already resolve the lazy message if it needs transformation (else we cannot transform it)
            // after transform wrap it in a "lazy" closure again {{🤗}}
            transformMessage?.invoke(message?.invoke())?.let { { it } } ?: message
        )
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.logging

import kotlin.native.concurrent.SharedImmutable

@SharedImmutable
val logLevel = arrayOf(
    ru.pocketbyte.kydra.log.LogLevel.DEBUG,
    ru.pocketbyte.kydra.log.LogLevel.INFO,
    ru.pocketbyte.kydra.log.LogLevel.WARNING,
    ru.pocketbyte.kydra.log.LogLevel.ERROR
)

fun LogLevel.logLevel(): ru.pocketbyte.kydra.log.LogLevel {
    return logLevel[this.ordinal]
    // optimization for:
    // return when(this) {
    //     LogLevel.DEBUG -> ru.pocketbyte.kydra.log.LogLevel.DEBUG
    //     LogLevel.INFO -> ru.pocketbyte.kydra.log.LogLevel.INFO
    //     LogLevel.WARNING -> ru.pocketbyte.kydra.log.LogLevel.WARNING
    //     LogLevel.ERROR -> ru.pocketbyte.kydra.log.LogLevel.ERROR
    // }
}

class KydraLogger(val kydraLogger: ru.pocketbyte.kydra.log.Logger) : Logger {
    override fun log(level: LogLevel, tag: String?, throwable: Throwable?, message: (() -> String)?) {
        if (message != null)
            kydraLogger.log(level.logLevel(), tag, message())
        if (throwable != null)
            kydraLogger.log(level.logLevel(), tag, throwable)
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.logging

// the default logger should be set per platform
expect val defaultLogger: Logger

/**
 * The standard logger.
 *
 * This is used by the package level logging methods, and might be used directly as well
 */
expect var logger: Logger // how the actual logger is set can also differ per platform (e.g. Atomic references on native) so we expect it

/**
 * Reset the standard logger to the default
 */
fun resetLogger() {
    logger = defaultLogger
}

fun wrapLogger(wrapper: (Logger) -> Logger) {
    logger = wrapper(logger)
}

/**
 * Writes log with provided level and tag.
 * @param level Log level
 * @param tag Log tag. Optional.
 * @param throwable Error or Exception. Optional
 * @param message Message to be written into log
 */
fun log(level: LogLevel, tag: String? = null, throwable: Throwable? = null, message: (() -> String)?) {
    logger.log(level, tag, throwable, message)
}

// DEBUG //////////////////////////////////////////////////////////////

/**
 * Writes log with DEBUG log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Message to be written into log.
 */
fun debug(tag: String?, message: String) {
    log(LogLevel.DEBUG, tag) { message }
}

/**
 * Writes log with DEBUG log level and empty tag.
 * @param message Message to be written into log.
 */
fun debug(message: String) {
    log(LogLevel.DEBUG) { message }
}

/**
 * Log exception with DEBUG log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception to be written into log.
 */
fun debug(tag: String? = null, throwable: Throwable) {
    log(LogLevel.DEBUG, tag, throwable, null)
}

/**
 * Log exception with DEBUG log level and empty tag.
 * @param throwable Error or Exception to be written into log.
 */
fun debug(throwable: Throwable) = log(LogLevel.DEBUG, throwable = throwable, message = null)

/**
 * Writes log with DEBUG log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Function that returns message to be written into log
 */
fun debug(tag: String?, message: () -> String) = log(LogLevel.DEBUG, tag, message = message)

/**
 * Writes log with DEBUG log level and empty tag.
 * @param message Function that returns message to be written into log
 */
fun debug(message: () -> String) = log(LogLevel.DEBUG, message = message)

/**
 * Writes log with DEBUG log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception. Optional
 * @param message Message to be written into log.
 */
fun debug(tag: String? = null, throwable: Throwable? = null, message: () -> String) =
    log(LogLevel.DEBUG, tag, throwable, message)

/**
 * Writes log with DEBUG log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Message to be written into log.
 */
fun d(tag: String?, message: String) {
    log(LogLevel.DEBUG, tag) { message }
}

/**
 * Writes log with DEBUG log level and empty tag.
 * @param message Message to be written into log.
 */
fun d(message: String) {
    log(LogLevel.DEBUG) { message }
}

/**
 * Log exception with DEBUG log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception to be written into log.
 */
fun d(tag: String? = null, throwable: Throwable) {
    log(LogLevel.DEBUG, tag, throwable, null)
}

/**
 * Log exception with DEBUG log level and empty tag.
 * @param throwable Error or Exception to be written into log.
 */
fun d(throwable: Throwable) = log(LogLevel.DEBUG, throwable = throwable, message = null)

/**
 * Writes log with DEBUG log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Function that returns message to be written into log
 */
fun d(tag: String?, message: () -> String) = log(LogLevel.DEBUG, tag, message = message)

/**
 * Writes log with DEBUG log level and empty tag.
 * @param message Function that returns message to be written into log
 */
fun d(message: () -> String) = log(LogLevel.DEBUG, message = message)

/**
 * Writes log with DEBUG log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception. Optional
 * @param message Message to be written into log.
 */
fun d(tag: String? = null, throwable: Throwable? = null, message: () -> String) = log(LogLevel.DEBUG, tag, throwable, message)

// INFO /////////////////////////////////////////////////////////////////

/**
 * Writes log with INFO log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Message to be written into log.
 */
fun info(tag: String?, message: String) {
    log(LogLevel.INFO, tag) { message }
}

/**
 * Writes log with INFO log level and empty tag.
 * @param message Message to be written into log.
 */
fun info(message: String) {
    log(LogLevel.INFO) { message }
}

/**
 * Log exception with INFO log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception to be written into log.
 */
fun info(tag: String? = null, throwable: Throwable) {
    log(LogLevel.INFO, tag, throwable, null)
}

/**
 * Log exception with INFO log level and empty tag.
 * @param throwable Error or Exception to be written into log.
 */
fun info(throwable: Throwable) = log(LogLevel.INFO, throwable = throwable, message = null)

/**
 * Writes log with INFO log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Function that returns message to be written into log
 */
fun info(tag: String?, message: () -> String) = log(LogLevel.INFO, tag, message = message)

/**
 * Writes log with INFO log level and empty tag.
 * @param message Function that returns message to be written into log
 */
fun info(message: () -> String) = log(LogLevel.INFO, message = message)

/**
 * Writes log with INFO log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception. Optional
 * @param message Message to be written into log.
 */
fun info(tag: String? = null, throwable: Throwable? = null, message: () -> String) =
    log(LogLevel.INFO, tag, throwable, message)

/**
 * Writes log with INFO log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Message to be written into log.
 */
fun i(tag: String?, message: String) {
    log(LogLevel.INFO, tag) { message }
}

/**
 * Writes log with INFO log level and empty tag.
 * @param message Message to be written into log.
 */
fun i(message: String) {
    log(LogLevel.INFO) { message }
}

/**
 * Log exception with INFO log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception to be written into log.
 */
fun i(tag: String? = null, throwable: Throwable) {
    log(LogLevel.INFO, tag, throwable, null)
}

/**
 * Log exception with INFO log level and empty tag.
 * @param throwable Error or Exception to be written into log.
 */
fun i(throwable: Throwable) = log(LogLevel.INFO, throwable = throwable, message = null)

/**
 * Writes log with INFO log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Function that returns message to be written into log
 */
fun i(tag: String?, message: () -> String) = log(LogLevel.INFO, tag, message = message)

/**
 * Writes log with INFO log level and empty tag.
 * @param message Function that returns message to be written into log
 */
fun i(message: () -> String) = log(LogLevel.INFO, message = message)

/**
 * Writes log with INFO log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception. Optional
 * @param message Message to be written into log.
 */
fun i(tag: String? = null, throwable: Throwable? = null, message: () -> String) = log(LogLevel.INFO, tag, throwable, message)

// WARN /////////////////////////////////////////////////////////////////

/**
 * Writes log with WARN log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Message to be written into log.
 */
fun warn(tag: String?, message: String) {
    log(LogLevel.WARN, tag) { message }
}

/**
 * Writes log with WARN log level and empty tag.
 * @param message Message to be written into log.
 */
fun warn(message: String) {
    log(LogLevel.WARN) { message }
}

/**
 * Log exception with WARN log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception to be written into log.
 */
fun warn(tag: String? = null, throwable: Throwable) {
    log(LogLevel.WARN, tag, throwable, null)
}

/**
 * Log exception with WARN log level and empty tag.
 * @param throwable Error or Exception to be written into log.
 */
fun warn(throwable: Throwable) = log(LogLevel.WARN, throwable = throwable, message = null)

/**
 * Writes log with WARN log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Function that returns message to be written into log
 */
fun warn(tag: String?, message: () -> String) = log(LogLevel.WARN, tag, message = message)

/**
 * Writes log with WARN log level and empty tag.
 * @param message Function that returns message to be written into log
 */
fun warn(message: () -> String) = log(LogLevel.WARN, message = message)

/**
 * Writes log with WARN log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception. Optional
 * @param message Message to be written into log.
 */
fun warn(tag: String? = null, throwable: Throwable? = null, message: () -> String) =
    log(LogLevel.WARN, tag, throwable, message)

/**
 * Writes log with WARN log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Message to be written into log.
 */
fun w(tag: String?, message: String) {
    log(LogLevel.WARN, tag) { message }
}

/**
 * Writes log with WARN log level and empty tag.
 * @param message Message to be written into log.
 */
fun w(message: String) {
    log(LogLevel.WARN) { message }
}

/**
 * Log exception with WARN log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception to be written into log.
 */
fun w(tag: String? = null, throwable: Throwable) {
    log(LogLevel.WARN, tag, throwable, null)
}

/**
 * Log exception with WARN log level and empty tag.
 * @param throwable Error or Exception to be written into log.
 */
fun w(throwable: Throwable) = log(LogLevel.WARN, throwable = throwable, message = null)

/**
 * Writes log with WARN log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Function that returns message to be written into log
 */
fun w(tag: String?, message: () -> String) = log(LogLevel.WARN, tag, message = message)

/**
 * Writes log with WARN log level and empty tag.
 * @param message Function that returns message to be written into log
 */
fun w(message: () -> String) = log(LogLevel.WARN, message = message)

/**
 * Writes log with WARN log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception. Optional
 * @param message Message to be written into log.
 */
fun w(tag: String? = null, throwable: Throwable? = null, message: () -> String) = log(LogLevel.WARN, tag, throwable, message)

// ERROR ////////////////////////////////////////////////////////////////

/**
 * Writes log with ERROR log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Message to be written into log.
 */
fun error(tag: String?, message: String) {
    log(LogLevel.ERROR, tag) { message }
}

/**
 * Writes log with ERROR log level and empty tag.
 * @param message Message to be written into log.
 */
fun error(message: String) {
    log(LogLevel.ERROR) { message }
}

/**
 * Log exception with ERROR log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception to be written into log.
 */
fun error(tag: String? = null, throwable: Throwable) {
    log(LogLevel.ERROR, tag, throwable, null)
}

/**
 * Log exception with ERROR log level and empty tag.
 * @param throwable Error or Exception to be written into log.
 */
fun error(throwable: Throwable) = log(LogLevel.ERROR, throwable = throwable, message = null)

/**
 * Writes log with ERROR log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Function that returns message to be written into log
 */
fun error(tag: String?, message: () -> String) = log(LogLevel.ERROR, tag, message = message)

/**
 * Writes log with ERROR log level and empty tag.
 * @param message Function that returns message to be written into log
 */
fun error(message: () -> String) = log(LogLevel.ERROR, message = message)

/**
 * Writes log with ERROR log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception. Optional
 * @param message Message to be written into log.
 */
fun error(tag: String? = null, throwable: Throwable? = null, message: () -> String) =
    log(LogLevel.ERROR, tag, throwable, message)

/**
 * Writes log with ERROR log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Message to be written into log.
 */
fun e(tag: String?, message: String) {
    log(LogLevel.ERROR, tag) { message }
}

/**
 * Writes log with ERROR log level and empty tag.
 * @param message Message to be written into log.
 */
fun e(message: String) {
    log(LogLevel.ERROR) { message }
}

/**
 * Log exception with ERROR log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception to be written into log.
 */
fun e(tag: String? = null, throwable: Throwable) {
    log(LogLevel.ERROR, tag, throwable, null)
}

/**
 * Log exception with ERROR log level and empty tag.
 * @param throwable Error or Exception to be written into log.
 */
fun e(throwable: Throwable) = log(LogLevel.ERROR, throwable = throwable, message = null)

/**
 * Writes log with ERROR log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param message Function that returns message to be written into log
 */
fun e(tag: String?, message: () -> String) = log(LogLevel.ERROR, tag, message = message)

/**
 * Writes log with ERROR log level and empty tag.
 * @param message Function that returns message to be written into log
 */
fun e(message: () -> String) = log(LogLevel.ERROR, message = message)

/**
 * Writes log with ERROR log level and provided tag.
 * @param tag Tag of the log record. Optional
 * @param throwable Error or Exception. Optional
 * @param message Message to be written into log.
 */
fun e(tag: String? = null, throwable: Throwable? = null, message: () -> String) = log(LogLevel.ERROR, tag, throwable, message)

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.logging

import co.touchlab.stately.concurrency.value
import ru.pocketbyte.kydra.log.NSLogger
import kotlin.native.concurrent.SharedImmutable

@SharedImmutable
actual val defaultLogger: Logger = TransformLogger(
    KydraLogger(NSLogger()),
    transformMessage = {
        // Since on iOS Kydra uses NSLog, the messages is formatted. % signs should therefore be escaped
        it?.replace("%", "%%")
    }
)

@SharedImmutable
private val _logger = co.touchlab.stately.concurrency.AtomicReference(defaultLogger)
actual var logger
    get() = _logger.value
    set(value) { _logger.value = value }

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.logging

import ru.pocketbyte.kydra.log.JsLogger

actual val defaultLogger: Logger = KydraLogger(JsLogger())
actual var logger = defaultLogger

/*
 Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.

*/
@file:JvmName("JvmLogKt")
package com.splendo.kaluga.logging

import ru.pocketbyte.kydra.log.PrintLogger

actual val defaultLogger: Logger = KydraLogger(PrintLogger())
actual var logger = defaultLogger

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.microphone

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import com.splendo.kaluga.permissions.AndroidPermissionsManager
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class MicrophonePermissionManager(
    context: Context,
    stateRepo: MicrophonePermissionStateRepo
) : PermissionManager<MicrophonePermission>(stateRepo) {

    private val permissionsManager = AndroidPermissionsManager(context, this, arrayOf(Manifest.permission.RECORD_AUDIO))
    private val supported = context.packageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)

    override suspend fun requestPermission() {
        if (supported)
            permissionsManager.requestPermissions()
    }

    override suspend fun initializeState(): PermissionState<MicrophonePermission> {
        return when {
            !supported -> PermissionState.Denied.Locked()
            permissionsManager.hasPermissions -> PermissionState.Allowed()
            else -> PermissionState.Denied.Requestable()
        }
    }

    override suspend fun startMonitoring(interval: Long) {
        if (supported)
            permissionsManager.startMonitoring(interval)
    }

    override suspend fun stopMonitoring() {
        if (supported)
            permissionsManager.stopMonitoring()
    }
}

actual class MicrophonePermissionManagerBuilder actual constructor(private val context: PermissionContext) : BaseMicrophonePermissionManagerBuilder {

    override fun create(repo: MicrophonePermissionStateRepo): PermissionManager<MicrophonePermission> {
        return MicrophonePermissionManager(context.context, repo)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.microphone

import com.splendo.kaluga.permissions.BasePermissionsBuilder
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionStateRepo
import com.splendo.kaluga.permissions.defaultPermissionContext
import kotlin.coroutines.CoroutineContext

/**
 * A [PermissionManager] for managing [MicrophonePermission]
 */
expect class MicrophonePermissionManager : PermissionManager<MicrophonePermission>

interface BaseMicrophonePermissionManagerBuilder : BasePermissionsBuilder {

    /**
     * Creates a [MicrophonePermissionManager]
     * @param repo The [MicrophonePermissionStateRepo] associated with the [MicrophonePermission]
     */
    fun create(repo: MicrophonePermissionStateRepo): PermissionManager<MicrophonePermission>
}

/**
 * A builder for creating a [MicrophonePermissionManager]
 */
expect class MicrophonePermissionManagerBuilder(context: PermissionContext = defaultPermissionContext) : BaseMicrophonePermissionManagerBuilder

/**
 * A [PermissionStateRepo] for [MicrophonePermission]
 * @param builder The [MicrophonePermissionManagerBuilder] for creating the [MicrophonePermissionManager] associated with the permission
 * @param coroutineContext The [CoroutineContext] to run the state machine on.
 */
class MicrophonePermissionStateRepo(builder: BaseMicrophonePermissionManagerBuilder, coroutineContext: CoroutineContext) : PermissionStateRepo<MicrophonePermission>(coroutineContext = coroutineContext) {

    override val permissionManager: PermissionManager<MicrophonePermission> = builder.create(this)
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.microphone

import com.splendo.kaluga.permissions.Permission
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.defaultPermissionContext

/**
 * Permission to access the users Microphone
 */
object MicrophonePermission : Permission()

fun PermissionsBuilder.registerMicrophonePermission() =
    registerMicrophonePermissionBuilder(context).also { builder ->
        registerPermissionStateRepoBuilder(MicrophonePermission::class) { _, coroutineContext ->
            MicrophonePermissionStateRepo(builder as BaseMicrophonePermissionManagerBuilder, coroutineContext)
        }
    }

internal fun PermissionsBuilder.registerMicrophonePermissionBuilder(context: PermissionContext = defaultPermissionContext): MicrophonePermissionManagerBuilder = register(
    builder = MicrophonePermissionManagerBuilder(context),
    permission = MicrophonePermission::class
)

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.microphone

import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState
import com.splendo.kaluga.permissions.av.AVPermissionHelper
import com.splendo.kaluga.permissions.microphone.av.AVTypeMicrophone
import platform.Foundation.NSBundle

actual class MicrophonePermissionManager(
    private val bundle: NSBundle,
    stateRepo: MicrophonePermissionStateRepo
) : PermissionManager<MicrophonePermission>(stateRepo) {

    private val avPermissionHelper = AVPermissionHelper(bundle, AVTypeMicrophone(this))

    override suspend fun requestPermission() {
        avPermissionHelper.requestPermission()
    }

    override suspend fun initializeState(): PermissionState<MicrophonePermission> {
        return avPermissionHelper.initializeState()
    }

    override suspend fun startMonitoring(interval: Long) {
        avPermissionHelper.startMonitoring(interval)
    }

    override suspend fun stopMonitoring() {
        avPermissionHelper.stopMonitoring()
    }
}

actual class MicrophonePermissionManagerBuilder actual constructor(private val context: PermissionContext) : BaseMicrophonePermissionManagerBuilder {

    override fun create(repo: MicrophonePermissionStateRepo): PermissionManager<MicrophonePermission> {
        return MicrophonePermissionManager(context, repo)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.microphone

import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class MicrophonePermissionManager(repo: MicrophonePermissionStateRepo) : PermissionManager<MicrophonePermission>(repo) {

    override suspend fun requestPermission() {
        TODO("not implemented")
    }

    override suspend fun initializeState(): PermissionState<MicrophonePermission> {
        TODO("not implemented")
    }

    override suspend fun startMonitoring(interval: Long) {
        TODO("not implemented")
    }

    override suspend fun stopMonitoring() {
        TODO("not implemented")
    }
}

actual class MicrophonePermissionManagerBuilder actual constructor(context: PermissionContext) : BaseMicrophonePermissionManagerBuilder {

    override fun create(repo: MicrophonePermissionStateRepo): PermissionManager<MicrophonePermission> {
        return MicrophonePermissionManager(repo)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.notifications

import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class NotificationOptions

actual class NotificationsPermissionManager(
    actual val notifications: NotificationsPermission,
    stateRepo: NotificationsPermissionStateRepo
) : PermissionManager<NotificationsPermission>(stateRepo) {

    override suspend fun requestPermission() {
        // No need to do anything, permission always granted
    }

    override suspend fun initializeState(): PermissionState<NotificationsPermission> {
        // Permission always granted
        return PermissionState.Allowed()
    }

    override suspend fun startMonitoring(interval: Long) {
        // No need to do anything, permission always granted
    }

    override suspend fun stopMonitoring() {
    }
}

actual class NotificationsPermissionManagerBuilder actual constructor(context: PermissionContext) : BaseNotificationsPermissionManagerBuilder {

    override fun create(notifications: NotificationsPermission, repo: NotificationsPermissionStateRepo): PermissionManager<NotificationsPermission> {
        return NotificationsPermissionManager(notifications, repo)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.notifications

import com.splendo.kaluga.permissions.BasePermissionsBuilder
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionStateRepo
import com.splendo.kaluga.permissions.defaultPermissionContext
import kotlin.coroutines.CoroutineContext

/**
 * Options for configuring a [NotificationsPermission]
 */
expect class NotificationOptions

/**
 * A [PermissionManager] for managing [NotificationsPermission]
 */
expect class NotificationsPermissionManager : PermissionManager<NotificationsPermission> {
    /**
     * The [NotificationsPermission] managed by this manager.
     */
    val notifications: NotificationsPermission
}

interface BaseNotificationsPermissionManagerBuilder : BasePermissionsBuilder {
    /**
     * Creates a [NotificationsPermissionManager]
     * @param repo The [NotificationsPermissionStateRepo] associated with the [NotificationsPermission]
     */
    fun create(notifications: NotificationsPermission, repo: NotificationsPermissionStateRepo): PermissionManager<NotificationsPermission>
}

/**
 * A builder for creating a [NotificationsPermissionManager]
 */
expect class NotificationsPermissionManagerBuilder(context: PermissionContext = defaultPermissionContext) : BaseNotificationsPermissionManagerBuilder

/**
 * A [PermissionStateRepo] for [NotificationsPermission]
 * @param builder The [NotificationsPermissionManagerBuilder] for creating the [NotificationsPermissionManager] associated with the permission
 * @param coroutineContext The [CoroutineContext] to run the state machine on.
 */
class NotificationsPermissionStateRepo(notifications: NotificationsPermission, builder: BaseNotificationsPermissionManagerBuilder, coroutineContext: CoroutineContext) : PermissionStateRepo<NotificationsPermission>(coroutineContext = coroutineContext) {

    override val permissionManager: PermissionManager<NotificationsPermission> = builder.create(notifications, this)
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.notifications

import com.splendo.kaluga.permissions.Permission
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.defaultPermissionContext

/**
 * Permission to access the users Notifications.
 * @param options The [NotificationOptions] determining the type of notifications that can be accessed
 */
data class NotificationsPermission(val options: NotificationOptions? = null) : Permission()

fun PermissionsBuilder.registerNotificationsPermission() =
    registerNotificationsPermissionBuilder(context).also { builder ->
        registerPermissionStateRepoBuilder(NotificationsPermission::class) { permission, coroutineContext ->
            NotificationsPermissionStateRepo(permission as NotificationsPermission, builder as BaseNotificationsPermissionManagerBuilder, coroutineContext)
        }
    }

internal fun PermissionsBuilder.registerNotificationsPermissionBuilder(context: PermissionContext = defaultPermissionContext): NotificationsPermissionManagerBuilder = register(builder = NotificationsPermissionManagerBuilder(context), permission = NotificationsPermission::class)

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.notifications

import com.splendo.kaluga.base.mainContinuation
import com.splendo.kaluga.logging.error
import com.splendo.kaluga.permissions.IOSPermissionsHelper
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionRefreshScheduler
import com.splendo.kaluga.permissions.PermissionState
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.launch
import platform.UserNotifications.UNAuthorizationOptionNone
import platform.UserNotifications.UNAuthorizationOptions
import platform.UserNotifications.UNAuthorizationStatus
import platform.UserNotifications.UNAuthorizationStatusAuthorized
import platform.UserNotifications.UNAuthorizationStatusDenied
import platform.UserNotifications.UNAuthorizationStatusNotDetermined
import platform.UserNotifications.UNAuthorizationStatusProvisional
import platform.UserNotifications.UNUserNotificationCenter

actual data class NotificationOptions(val options: UNAuthorizationOptions)

actual class NotificationsPermissionManager(
    actual val notifications: NotificationsPermission,
    stateRepo: NotificationsPermissionStateRepo
) : PermissionManager<NotificationsPermission>(stateRepo) {

    private val notificationCenter = UNUserNotificationCenter.currentNotificationCenter()
    private var authorization: suspend () -> IOSPermissionsHelper.AuthorizationStatus = {
        val authorizationStatus = CompletableDeferred<IOSPermissionsHelper.AuthorizationStatus>()
        launch {
            notificationCenter.getNotificationSettingsWithCompletionHandler(
                mainContinuation { settings ->
                    authorizationStatus.complete(settings?.authorizationStatus?.toAuthorizationStatus() ?: IOSPermissionsHelper.AuthorizationStatus.NotDetermined)
                }
            )
        }
        authorizationStatus.await()
    }
    private val timerHelper = PermissionRefreshScheduler(this, authorization)

    override suspend fun requestPermission() {
        timerHelper.isWaiting.value = true
        notificationCenter.requestAuthorizationWithOptions(
            notifications.options?.options ?: UNAuthorizationOptionNone,
            mainContinuation { authorization, error ->
                timerHelper.isWaiting.value = false
                error?.let {
                    revokePermission(true)
                } ?: run {
                    if (authorization) grantPermission() else revokePermission(true)
                }
            }
        )
    }

    override suspend fun initializeState(): PermissionState<NotificationsPermission> {
        return IOSPermissionsHelper.getPermissionState(authorization())
    }

    override suspend fun startMonitoring(interval: Long) {
        timerHelper.startMonitoring(interval)
    }

    override suspend fun stopMonitoring() {
        timerHelper.stopMonitoring()
    }
}

actual class NotificationsPermissionManagerBuilder actual constructor(context: PermissionContext) : BaseNotificationsPermissionManagerBuilder {

    override fun create(notifications: NotificationsPermission, repo: NotificationsPermissionStateRepo): PermissionManager<NotificationsPermission> {
        return NotificationsPermissionManager(notifications, repo)
    }
}

private fun UNAuthorizationStatus.toAuthorizationStatus(): IOSPermissionsHelper.AuthorizationStatus {
    return when (this) {
        UNAuthorizationStatusAuthorized -> IOSPermissionsHelper.AuthorizationStatus.Authorized
        UNAuthorizationStatusDenied -> IOSPermissionsHelper.AuthorizationStatus.Denied
        UNAuthorizationStatusProvisional -> IOSPermissionsHelper.AuthorizationStatus.Restricted
        UNAuthorizationStatusNotDetermined -> IOSPermissionsHelper.AuthorizationStatus.NotDetermined
        else -> {
            error(
                "CalendarPermissionManager",
                "Unknown CBManagerAuthorization status={$this}"
            )
            IOSPermissionsHelper.AuthorizationStatus.NotDetermined
        }
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.notifications

import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class NotificationOptions

actual class NotificationsPermissionManager(actual val notifications: NotificationsPermission, repo: NotificationsPermissionStateRepo) : PermissionManager<NotificationsPermission>(repo) {

    override suspend fun requestPermission() {
        TODO("not implemented")
    }

    override suspend fun initializeState(): PermissionState<NotificationsPermission> {
        TODO("not implemented")
    }

    override suspend fun startMonitoring(interval: Long) {
        TODO("not implemented")
    }

    override suspend fun stopMonitoring() {
        TODO("not implemented")
    }
}

actual class NotificationsPermissionManagerBuilder actual constructor(context: PermissionContext) : BaseNotificationsPermissionManagerBuilder {

    override fun create(notifications: NotificationsPermission, repo: NotificationsPermissionStateRepo): PermissionManager<NotificationsPermission> {
        return NotificationsPermissionManager(notifications, repo)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.compose

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.splendo.kaluga.resources.DefaultColors
import com.splendo.kaluga.resources.stylable.BackgroundStyle
import com.splendo.kaluga.resources.stylable.GradientStyle

fun Modifier.backgroundStyle(backgroundStyle: BackgroundStyle) = background(
    backgroundStyle.fillStyle.brush,
    backgroundStyle.shape.shape
).border(backgroundStyle.strokeStyle.borderStroke, backgroundStyle.shape.shape)

val BackgroundStyle.FillStyle.brush: Brush get() = when (this) {
    is BackgroundStyle.FillStyle.Solid -> SolidColor(color.composable)
    is BackgroundStyle.FillStyle.Gradient -> gradientStyle.brush
}

val BackgroundStyle.Shape.shape: Shape get() = when (this) {
    is BackgroundStyle.Shape.Rectangle -> RoundedCornerRadiusShape(
        if (roundedCorners.contains(BackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT)) CornerRadiusSize(cornerRadiusX.dp, cornerRadiusY.dp) else CornerRadiusSize(0.dp, 0.dp),
        if (roundedCorners.contains(BackgroundStyle.Shape.Rectangle.Corner.TOP_RIGHT)) CornerRadiusSize(cornerRadiusX.dp, cornerRadiusY.dp) else CornerRadiusSize(0.dp, 0.dp),
        if (roundedCorners.contains(BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_RIGHT)) CornerRadiusSize(cornerRadiusX.dp, cornerRadiusY.dp) else CornerRadiusSize(0.dp, 0.dp),
        if (roundedCorners.contains(BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_LEFT)) CornerRadiusSize(cornerRadiusX.dp, cornerRadiusY.dp) else CornerRadiusSize(0.dp, 0.dp)
    )
    is BackgroundStyle.Shape.Oval -> androidx.compose.foundation.shape.CircleShape
}

val BackgroundStyle.StrokeStyle.borderStroke: BorderStroke get() = when (this) {
    is BackgroundStyle.StrokeStyle.None -> BorderStroke(0.0f.dp, Color.Transparent)
    is BackgroundStyle.StrokeStyle.Stroke -> BorderStroke(width.dp, color.composable)
}

@Preview
@Composable
fun PreviewBackgroundStyle() {
    Box(
        modifier = Modifier.backgroundStyle(
            BackgroundStyle(
                BackgroundStyle.FillStyle.Gradient(
                    GradientStyle.Radial(
                        listOf(
                            GradientStyle.ColorPoint(DefaultColors.darkBlue, 0.2f),
                            GradientStyle.ColorPoint(DefaultColors.blue, 0.4f),
                            GradientStyle.ColorPoint(DefaultColors.aliceBlue, 0.6f)
                        ),
                        50.0f,
                        GradientStyle.CenterPoint(0.2f, 0.3f)
                    )
                ),
                BackgroundStyle.StrokeStyle.Stroke(1.0f, DefaultColors.black),
                BackgroundStyle.Shape.Rectangle(4.0f, 2.0f, setOf(BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_LEFT, BackgroundStyle.Shape.Rectangle.Corner.TOP_RIGHT))
            )
        )
    ) {
        Text("Test")
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.compose

import androidx.compose.ui.graphics.Color

val com.splendo.kaluga.resources.Color.composable get() = Color(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.compose

import androidx.compose.runtime.Immutable
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.RadialGradientShader
import androidx.compose.ui.graphics.Shader
import androidx.compose.ui.graphics.ShaderBrush
import androidx.compose.ui.graphics.SweepGradientShader
import androidx.compose.ui.graphics.TileMode
import com.splendo.kaluga.resources.stylable.GradientStyle

val GradientStyle.brush: Brush
    get() = when (this) {
        is GradientStyle.Linear -> Brush.linearGradient(
            *colorPoints.colorStops.toTypedArray(),
            start = orientation.offset.first,
            end = orientation.offset.second
        )
        is GradientStyle.Radial -> RelativeRadialGradient(
            centerPoint,
            radius = radius,
            colorStops = colorPoints.colorStops.toTypedArray()
        )
        is GradientStyle.Angular -> RelativeSweepGradient(
            centerPoint,
            *colorPoints.colorStops.toTypedArray()
        )
    }

private val List<GradientStyle.ColorPoint>.colorStops: List<Pair<Float, Color>>
    get() = map {
        Pair(
            it.offset,
            it.color.composable
        )
    }
private val GradientStyle.Linear.Orientation.offset: Pair<Offset, Offset>
    get() = when (this) {
        GradientStyle.Linear.Orientation.BOTTOM_LEFT_TOP_RIGHT -> Pair(
            Offset(0.0f, Float.POSITIVE_INFINITY),
            Offset(Float.POSITIVE_INFINITY, 0.0f)
        )
        GradientStyle.Linear.Orientation.BOTTOM_TOP -> Pair(
            Offset(0.0f, Float.POSITIVE_INFINITY),
            Offset(0.0f, Float.POSITIVE_INFINITY)
        )
        GradientStyle.Linear.Orientation.BOTTOM_RIGHT_TOP_LEFT -> Pair(
            Offset(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY),
            Offset(0.0f, 0.0f)
        )
        GradientStyle.Linear.Orientation.LEFT_RIGHT -> Pair(
            Offset(0.0f, 0.0f),
            Offset(Float.POSITIVE_INFINITY, 0.0f)
        )
        GradientStyle.Linear.Orientation.RIGHT_LEFT -> Pair(
            Offset(Float.POSITIVE_INFINITY, 0.0f),
            Offset(0.0f, 0.0f)
        )
        GradientStyle.Linear.Orientation.TOP_LEFT_BOTTOM_RIGHT -> Pair(
            Offset(0.0f, 0.0f),
            Offset(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)
        )
        GradientStyle.Linear.Orientation.TOP_BOTTOM -> Pair(
            Offset(0.0f, 0.0f),
            Offset(0.0f, Float.POSITIVE_INFINITY)
        )
        GradientStyle.Linear.Orientation.TOP_RIGHT_BOTTOM_LEFT -> Pair(
            Offset(Float.POSITIVE_INFINITY, 0.0f),
            Offset(0.0f, Float.POSITIVE_INFINITY)
        )
    }

@Immutable
class RelativeRadialGradient internal constructor(
    private val colors: List<Color>,
    private val stops: List<Float>? = null,
    private val center: GradientStyle.CenterPoint,
    private val radius: Float,
    private val tileMode: TileMode = TileMode.Clamp
) : ShaderBrush() {

    constructor(
        center: GradientStyle.CenterPoint,
        radius: Float,
        tileMode: TileMode = TileMode.Clamp,
        vararg colorStops: Pair<Float, Color>
    ) : this(
        colors = List<Color>(colorStops.size) { i -> colorStops[i].second },
        stops = List<Float>(colorStops.size) { i -> colorStops[i].first },
        center = center,
        radius = radius,
        tileMode = tileMode
    )

    override fun createShader(size: Size): Shader {
        val centerX = center.x * size.width
        val centerY = center.y * size.height

        return RadialGradientShader(
            colors = colors,
            colorStops = stops,
            center = Offset(centerX, centerY),
            radius = if (radius == Float.POSITIVE_INFINITY) size.minDimension / 2 else radius,
            tileMode = tileMode
        )
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is RelativeRadialGradient) return false

        if (colors != other.colors) return false
        if (stops != other.stops) return false
        if (center != other.center) return false
        if (radius != other.radius) return false
        if (tileMode != other.tileMode) return false

        return true
    }

    override fun hashCode(): Int {
        var result = colors.hashCode()
        result = 31 * result + (stops?.hashCode() ?: 0)
        result = 31 * result + center.hashCode()
        result = 31 * result + radius.hashCode()
        result = 31 * result + tileMode.hashCode()
        return result
    }

    override fun toString(): String {
        val radiusValue = if (radius.isFinite()) "radius=$radius, " else ""
        return "RelativeRadialGradient(" +
            "colors=$colors, " +
            "stops=$stops, " +
            "center=$center, " +
            radiusValue +
            "tileMode=$tileMode)"
    }
}

/**
 * Brush implementation used to apply a sweep gradient on a given [Paint]
 */
@Immutable
class RelativeSweepGradient internal constructor(
    private val center: GradientStyle.CenterPoint,
    private val colors: List<Color>,
    private val stops: List<Float>? = null
) : ShaderBrush() {

    constructor(center: GradientStyle.CenterPoint, vararg colorStops: Pair<Float, Color>) : this(
        colors = List<Color>(colorStops.size) { i -> colorStops[i].second },
        stops = List<Float>(colorStops.size) { i -> colorStops[i].first },
        center = center
    )

    override fun createShader(size: Size): Shader =
        SweepGradientShader(
            Offset(
                center.x * size.width,
                center.y * size.height
            ),
            colors,
            stops
        )

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is RelativeSweepGradient) return false

        if (center != other.center) return false
        if (colors != other.colors) return false
        if (stops != other.stops) return false

        return true
    }

    override fun hashCode(): Int {
        var result = center.hashCode()
        result = 31 * result + colors.hashCode()
        result = 31 * result + (stops?.hashCode() ?: 0)
        return result
    }

    override fun toString(): String {
        return "RelativeSweepGradient(" +
            "center=$center, " +
            "colors=$colors, stops=$stops)"
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.compose

import android.graphics.Typeface
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.ButtonElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.splendo.kaluga.resources.DefaultColors
import com.splendo.kaluga.resources.StringStyleAttribute
import com.splendo.kaluga.resources.StyledStringBuilder
import com.splendo.kaluga.resources.stylable.BackgroundStyle
import com.splendo.kaluga.resources.stylable.ButtonStateStyle
import com.splendo.kaluga.resources.stylable.ButtonStyle
import com.splendo.kaluga.resources.styled
import com.splendo.kaluga.resources.view.KalugaButton
import com.splendo.kaluga.resources.view.KalugaLabel

@Composable
fun KalugaButton.Composable(
    modifier: Modifier,
    elevation: ButtonElevation = ButtonDefaults.elevation(),
    contentPadding: PaddingValues = ButtonDefaults.ContentPadding
) {
    val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
    val pressed by interactionSource.collectIsPressedAsState()
    val stateStyle = style.getStateStyle(isEnabled, pressed)
    val textStyle = style.getStateTextStyle(isEnabled, pressed)
    Button(
        modifier = modifier,
        enabled = isEnabled,
        elevation = elevation,
        colors = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent),
        shape = stateStyle.backgroundStyle.shape.shape,
        contentPadding = PaddingValues(0.dp),
        interactionSource = interactionSource,
        onClick = action,
    ) {
        Box(
            modifier = Modifier
                .backgroundStyle(stateStyle.backgroundStyle)
                .padding(contentPadding)
                .then(modifier)
        ) {
            when (this@Composable) {
                is KalugaButton.Plain -> KalugaLabel.Plain(text, textStyle)
                is KalugaButton.Styled -> KalugaLabel.Styled(text)
                else -> error("unknown button type")
            }.Composable(modifier = modifier)
        }
    }
}

@Composable
@Preview
fun PreviewKalugaButton() {
    val buttonStyle = ButtonStyle(
        font = Typeface.DEFAULT_BOLD,
        textSize = 14.0f,
        defaultStyle = ButtonStateStyle(
            DefaultColors.white,
            DefaultColors.red,
            shape = BackgroundStyle.Shape.Rectangle(5.0f)
        ),
        pressedStyle = ButtonStateStyle(
            DefaultColors.red,
            DefaultColors.white,
            shape = BackgroundStyle.Shape.Rectangle(5.0f)
        ),
        disabledStyle = ButtonStateStyle(
            DefaultColors.black,
            DefaultColors.white,
            shape = BackgroundStyle.Shape.Rectangle(5.0f)
        )
    )
    Column(modifier = Modifier.size(100.dp)) {
        KalugaButton.Plain("Plain Text", buttonStyle, true) {}.Composable(
            modifier = Modifier.fillMaxWidth()
        )
        KalugaButton.Styled(
            "Styled Text".styled(
                StyledStringBuilder.Provider(LocalContext.current),
                buttonStyle.getStateTextStyle(isEnabled = true, isPressed = false),
                {
                    Pair(StringStyleAttribute.CharacterStyleAttribute.ForegroundColor(DefaultColors.darkBlue), IntRange(0, 5))
                }
            ),
            buttonStyle,
            true
        ) {}.Composable(
            modifier = Modifier.fillMaxWidth()
        )
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.compose

import android.widget.TextView
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import com.splendo.kaluga.resources.DefaultColors
import com.splendo.kaluga.resources.StringStyleAttribute
import com.splendo.kaluga.resources.StyledStringBuilder
import com.splendo.kaluga.resources.defaultBoldFont
import com.splendo.kaluga.resources.styled
import com.splendo.kaluga.resources.view.KalugaLabel
import com.splendo.kaluga.resources.view.bindLabel

@Composable
fun KalugaLabel.Composable(modifier: Modifier) {
    when (this) {
        is KalugaLabel.Plain -> {
            Text(
                modifier = modifier,
                text = text,
                color = style.color.composable,
                fontFamily = FontFamily(style.font),
                fontSize = style.size.sp,
                textAlign = style.alignment.composable
            )
        }
        is KalugaLabel.Styled -> {
            // Compose does not support SpannedString and AttributedString lacks features so use legacy view
            AndroidView(
                modifier = modifier,
                factory = { context ->
                    TextView(context).apply {
                        this.bindLabel(this@Composable)
                    }
                }
            )
        }
    }
}

@Preview
@Composable
fun PreviewKalugaLabel() {
    val textStyle = com.splendo.kaluga.resources.stylable.TextStyle(
        defaultBoldFont,
        DefaultColors.darkRed,
        12.0f
    )
    Column(modifier = Modifier.size(100.dp)) {
        KalugaLabel.Plain("Plain Text", textStyle).Composable(
            modifier = Modifier.fillMaxWidth()
        )
        KalugaLabel.Styled(
            "Styled Text".styled(
                StyledStringBuilder.Provider(LocalContext.current),
                textStyle,
                {
                    Pair(
                        StringStyleAttribute.CharacterStyleAttribute.ForegroundColor(DefaultColors.darkBlue),
                        IntRange(0, 5)
                    )
                }
            ),
        ).Composable(
            modifier = Modifier.fillMaxWidth()
        )
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.compose

import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.InspectableValue
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection

@Immutable
interface CornerRadiusSize {
    fun xToPx(shapeSize: Size, density: Density): Float
    fun yToPx(shapeSize: Size, density: Density): Float
}

fun CornerRadiusSize.isValid(shapeSize: Size, density: Density): Boolean {
    return xToPx(shapeSize, density) >= 0.0f && yToPx(shapeSize, density) >= 0.0f
}

@Stable
fun CornerRadiusSize(x: Dp, y: Dp): CornerRadiusSize = DpCornerRadiusSize(x, y)

private data class DpCornerRadiusSize(private val radiusSize: RadiusSize) : CornerRadiusSize, InspectableValue {
    constructor(x: Dp, y: Dp) : this (RadiusSize(x, y))
    data class RadiusSize(val x: Dp, val y: Dp)
    override fun xToPx(shapeSize: Size, density: Density) =
        with(density) { radiusSize.x.toPx() }
    override fun yToPx(shapeSize: Size, density: Density) =
        with(density) { radiusSize.y.toPx() }

    override fun toString(): String = "CornerRadiusSize(x = ${radiusSize.x.value}.dp, y = ${radiusSize.y.value}.dp)"

    override val valueOverride: RadiusSize
        get() = radiusSize
}

class RoundedCornerRadiusShape(
    private val topStart: CornerRadiusSize,
    private val topEnd: CornerRadiusSize,
    private val bottomEnd: CornerRadiusSize,
    private val bottomStart: CornerRadiusSize
) : Shape {

    final override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density
    ): Outline {
        var topStartX = topStart.xToPx(size, density)
        var topStartY = topStart.yToPx(size, density)
        var topEndX = topEnd.xToPx(size, density)
        var topEndY = topEnd.yToPx(size, density)
        var bottomEndX = bottomEnd.xToPx(size, density)
        var bottomEndY = bottomEnd.yToPx(size, density)
        var bottomStartX = bottomStart.xToPx(size, density)
        var bottomStartY = bottomStart.yToPx(size, density)
        val minDimension = size.minDimension
        if (topStartX + bottomStartX > minDimension) {
            val scale = minDimension / (topStartX + bottomStartX)
            topStartX *= scale
            bottomStartX *= scale
        }
        if (topStartY + bottomStartY > minDimension) {
            val scale = minDimension / (topStartY + bottomStartY)
            topStartY *= scale
            bottomStartY *= scale
        }
        if (topEndX + bottomEndX > minDimension) {
            val scale = minDimension / (topEndX + bottomEndX)
            topEndX *= scale
            bottomEndX *= scale
        }
        if (topEndY + bottomEndY > minDimension) {
            val scale = minDimension / (topEndY + bottomEndY)
            topEndY *= scale
            bottomEndY *= scale
        }
        require(topStart.isValid(size, density) && topEnd.isValid(size, density) && bottomEnd.isValid(size, density) && bottomStart.isValid(size, density)) {
            "Corner size in Px can't be negative(topStart = $topStart, topEnd = $topEnd, " +
                "bottomEnd = $bottomEnd, bottomStart = $bottomStart)!"
        }
        return Outline.Rounded(
            if (layoutDirection == LayoutDirection.Ltr) {
                RoundRect(
                    rect = size.toRect(),
                    topLeft = CornerRadius(topStartX, topStartY),
                    topRight = CornerRadius(topEndX, topEndY),
                    bottomRight = CornerRadius(bottomEndX, bottomEndY),
                    bottomLeft = CornerRadius(bottomStartX, bottomStartY)
                )
            } else {
                RoundRect(
                    rect = size.toRect(),
                    topLeft = CornerRadius(topEndX, topEndY),
                    topRight = CornerRadius(topStartX, topStartY),
                    bottomRight = CornerRadius(bottomStartX, bottomStartY),
                    bottomLeft = CornerRadius(bottomEndX, bottomEndY)
                )
            }
        )
    }

    override fun toString(): String {
        return "RoundedCornerRadiusShape(topStart = $topStart, topEnd = $topEnd, bottomEnd = " +
            "$bottomEnd, bottomStart = $bottomStart)"
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is RoundedCornerShape) return false

        if (topStart != other.topStart) return false
        if (topEnd != other.topEnd) return false
        if (bottomEnd != other.bottomEnd) return false
        if (bottomStart != other.bottomStart) return false

        return true
    }

    override fun hashCode(): Int {
        var result = topStart.hashCode()
        result = 31 * result + topEnd.hashCode()
        result = 31 * result + bottomEnd.hashCode()
        result = 31 * result + bottomStart.hashCode()
        return result
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.compose

import androidx.compose.ui.text.style.TextAlign
import com.splendo.kaluga.resources.stylable.TextAlignment

val TextAlignment.composable: TextAlign
    get() = when (this) {
        TextAlignment.CENTER -> TextAlign.Center
        TextAlignment.LEFT -> TextAlign.Left
        TextAlignment.START -> TextAlign.Start
        TextAlignment.END -> TextAlign.End
        TextAlignment.RIGHT -> TextAlign.Right
    }

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

actual typealias Color = Int

actual val Color.red: Double get() = redInt.toDouble() / 255.0
actual val Color.redInt: Int get() = android.graphics.Color.red(this)
actual val Color.green: Double get() = greenInt.toDouble() / 255.0
actual val Color.greenInt: Int get() = android.graphics.Color.green(this)
actual val Color.blue: Double get() = blueInt.toDouble() / 255.0
actual val Color.blueInt: Int get() = android.graphics.Color.blue(this)
actual val Color.alpha: Double get() = alphaInt.toDouble() / 255.0
actual val Color.alphaInt: Int get() = android.graphics.Color.alpha(this)

actual fun colorFrom(red: Double, green: Double, blue: Double, alpha: Double): Color {
    return android.graphics.Color.argb((alpha * 255.0).toInt(), (red * 255.0).toInt(), (green * 255.0).toInt(), (blue * 255.0).toInt())
}

actual fun colorFrom(redInt: Int, greenInt: Int, blueInt: Int, alphaInt: Int): Color = android.graphics.Color.argb(alphaInt, redInt, greenInt, blueInt)

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import android.content.res.Configuration
import com.splendo.kaluga.base.ApplicationHolder

actual val isInDarkMode: Boolean get() = when (ApplicationHolder.applicationContext.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
    Configuration.UI_MODE_NIGHT_YES -> true
    else -> false
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.

 */

@file:JvmName("AndroidFont")
package com.splendo.kaluga.resources

import android.graphics.Typeface

actual typealias Font = Typeface

actual val defaultFont: Font get() = Typeface.DEFAULT
actual val defaultBoldFont: Font get() = Typeface.DEFAULT_BOLD
actual val defaultItalicFont: Font get() = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC)
actual val defaultMonospaceFont: Font get() = Typeface.MONOSPACE

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import android.graphics.drawable.Drawable
import androidx.core.graphics.drawable.DrawableCompat

actual data class Image(val drawable: Drawable)

actual fun Image.tinted(color: Color): Image? {
    return drawable.constantState?.newDrawable()?.mutate()?.let {
        val wrapped = DrawableCompat.wrap(it)
        DrawableCompat.setTint(wrapped, color)
        Image(wrapped)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import android.content.Context
import android.util.TypedValue

fun Float.dpToPixel(context: Context): Float = toPixel(TypedValue.COMPLEX_UNIT_DIP, context)
fun Float.spToPixel(context: Context): Float = toPixel(TypedValue.COMPLEX_UNIT_SP, context)

private fun Float.toPixel(unit: Int, context: Context): Float = TypedValue.applyDimension(
    unit,
    this,
    context.resources.displayMetrics
)

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import android.content.Context
import android.content.res.Resources
import android.graphics.Typeface
import android.os.Handler
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import com.splendo.kaluga.base.ApplicationHolder.Companion.application
import com.splendo.kaluga.base.ApplicationHolder.Companion.applicationContext
import kotlinx.coroutines.CompletableDeferred

actual class DefaultStringLoader(private val context: Context?) : StringLoader {
    actual constructor() : this(if (application != null) applicationContext else null)
    override fun loadString(identifier: String, defaultValue: String): String {
        if (context == null)
            return defaultValue
        val id = context.resources.getIdentifier(identifier, "string", context.packageName)
        return try {
            context.getString(id)
        } catch (e: Resources.NotFoundException) {
            defaultValue
        }
    }
    override fun loadQuantityString(identifier: String, quantity: Int, defaultValue: String): String {
        if (context == null)
            return defaultValue
        val id = context.resources.getIdentifier(identifier, "plurals", context.packageName)
        return try {
            context.resources.getQuantityString(id, quantity, quantity)
        } catch (e: Resources.NotFoundException) {
            defaultValue
        }
    }
}

actual class DefaultColorLoader(private val context: Context?) : ColorLoader {
    actual constructor() : this(if (application != null) applicationContext else null)
    override fun loadColor(identifier: String, defaultValue: Color?): Color? {
        if (context == null)
            return defaultValue
        val id = context.resources.getIdentifier(identifier, "color", context.packageName)
        return try {
            ContextCompat.getColor(context, id)
        } catch (e: Resources.NotFoundException) {
            defaultValue
        }
    }
}

actual class DefaultImageLoader(private val context: Context?) : ImageLoader {
    actual constructor() : this(if (application != null) applicationContext else null)
    override fun loadImage(identifier: String, defaultValue: Image?): Image? {
        if (context == null)
            return defaultValue
        val id = context.resources.getIdentifier(identifier, "drawable", context.packageName)
        return try {
            ContextCompat.getDrawable(context, id)?.let { Image(it) }
        } catch (e: Resources.NotFoundException) {
            defaultValue
        }
    }
}

actual class DefaultFontLoader(private val context: Context?, private val handler: Handler?) : FontLoader {
    actual constructor() : this(if (application != null) applicationContext else null, null)
    override suspend fun loadFont(identifier: String, defaultValue: Font?): Font? {
        if (context == null)
            return defaultValue
        val id = context.resources.getIdentifier(identifier, "font", context.packageName)
        return try {
            val deferredFont = CompletableDeferred<Typeface?>()
            val callback = object : ResourcesCompat.FontCallback() {
                override fun onFontRetrievalFailed(reason: Int) {
                    deferredFont.complete(defaultValue)
                }

                override fun onFontRetrieved(typeface: Typeface) {
                    deferredFont.complete(typeface)
                }
            }
            ResourcesCompat.getFont(context, id, callback, handler)
            deferredFont.await()
        } catch (e: Resources.NotFoundException) {
            defaultValue
        }
    }
}

package com.splendo.kaluga.resources

import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.text.Spannable
import android.text.SpannableString
import android.text.TextPaint
import android.text.style.AlignmentSpan
import android.text.style.BackgroundColorSpan
import android.text.style.CharacterStyle
import android.text.style.ForegroundColorSpan
import android.text.style.LeadingMarginSpan
import android.text.style.LineBackgroundSpan
import android.text.style.LineHeightSpan
import android.text.style.MetricAffectingSpan
import android.text.style.ParagraphStyle
import android.text.style.StrikethroughSpan
import android.text.style.SubscriptSpan
import android.text.style.SuperscriptSpan
import android.text.style.URLSpan
import android.text.style.UnderlineSpan
import com.splendo.kaluga.base.ApplicationHolder
import com.splendo.kaluga.resources.stylable.TextStyle
import com.splendo.kaluga.resources.view.alignment
import kotlin.math.min
import kotlin.math.roundToInt

actual class StyledString(val spannable: Spannable, actual val defaultTextStyle: TextStyle)

actual val StyledString.rawString: String get() = spannable.toString()

actual class StyledStringBuilder constructor(
    string: String,
    private val defaultTextStyle: TextStyle,
    private val context: Context
) {

    actual class Provider(private val context: Context = ApplicationHolder.applicationContext) {
        actual fun provide(string: String, defaultTextStyle: TextStyle) =
            StyledStringBuilder(string, defaultTextStyle, context)
    }

    private class CustomCharacterStyle(val modify: TextPaint.() -> Unit) : CharacterStyle() {
        override fun updateDrawState(tp: TextPaint?) {
            tp?.modify()
        }
    }

    private val builder = SpannableString(string)

    actual fun addStyleAttribute(attribute: StringStyleAttribute, range: IntRange) {
        builder.setSpan(
            when (attribute) {
                is StringStyleAttribute.CharacterStyleAttribute -> attribute.characterStyle(range)
                is StringStyleAttribute.ParagraphStyleAttribute -> attribute.paragraphStyle
                is StringStyleAttribute.Link -> URLSpan(attribute.url)
            },
            range.first,
            range.last + 1,
            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
        )
    }

    private fun StringStyleAttribute.CharacterStyleAttribute.characterStyle(range: IntRange): CharacterStyle =
        when (this) {
            is StringStyleAttribute.CharacterStyleAttribute.BackgroundColor -> BackgroundColorSpan(
                color
            )
            is StringStyleAttribute.CharacterStyleAttribute.ForegroundColor -> ForegroundColorSpan(
                color
            )
            is StringStyleAttribute.CharacterStyleAttribute.Font -> CustomCharacterStyle {
                typeface = font
                textSize = size.spToPixel(context)
            }
            is StringStyleAttribute.CharacterStyleAttribute.Kerning -> CustomCharacterStyle {
                letterSpacing = kern
            }
            is StringStyleAttribute.CharacterStyleAttribute.TextStyle -> CustomCharacterStyle {
                typeface = textStyle.font
                textSize = textStyle.size.spToPixel(context)
                color = textStyle.color
            }
            is StringStyleAttribute.CharacterStyleAttribute.Shadow -> CustomCharacterStyle {
                setShadowLayer(
                    blurRadius.spToPixel(context),
                    xOffset.spToPixel(context),
                    yOffset.spToPixel(context),
                    color
                )
            }
            is StringStyleAttribute.CharacterStyleAttribute.Strikethrough -> StrikethroughSpan()
            is StringStyleAttribute.CharacterStyleAttribute.SubScript -> SubscriptSpan()
            is StringStyleAttribute.CharacterStyleAttribute.SuperScript -> SuperscriptSpan()
            is StringStyleAttribute.CharacterStyleAttribute.Underline -> UnderlineSpan()
            is StringStyleAttribute.CharacterStyleAttribute.Stroke ->
                object : MetricAffectingSpan(), LineBackgroundSpan {
                    override fun updateMeasureState(textPaint: TextPaint) {
                        textPaint.style = Paint.Style.FILL_AND_STROKE
                        textPaint.strokeWidth = width
                    }

                    override fun updateDrawState(tp: TextPaint?) {
                        val textPaint = tp ?: return
                        textPaint.style = Paint.Style.FILL
                        textPaint.strokeWidth = width
                    }

                    override fun drawBackground(
                        canvas: Canvas,
                        paint: Paint,
                        left: Int,
                        right: Int,
                        top: Int,
                        baseline: Int,
                        bottom: Int,
                        text: CharSequence,
                        start: Int,
                        end: Int,
                        lineNumber: Int
                    ) {
                        val strokePaint = TextPaint(paint).apply {
                            style = Paint.Style.STROKE
                            strokeWidth = width
                            color = this@characterStyle.color
                        }

                        if (start < range.first) {
                            val offset = paint.measureText(text, start, range.first)
                            canvas.drawText(
                                text,
                                range.first,
                                min(range.last + 1, end),
                                left.toFloat() + offset,
                                baseline.toFloat(),
                                strokePaint
                            )
                        } else {
                            canvas.drawText(
                                text,
                                start,
                                min(range.last + 1, end),
                                left.toFloat(),
                                baseline.toFloat(),
                                strokePaint
                            )
                        }
                    }
                }
        }

    private val StringStyleAttribute.ParagraphStyleAttribute.paragraphStyle: ParagraphStyle
        get() = when (this) {
            is StringStyleAttribute.ParagraphStyleAttribute.LeadingIndent -> LeadingMarginSpan.Standard(
                firstLineIndent.toInt(),
                indent.toInt()
            )
            is StringStyleAttribute.ParagraphStyleAttribute.LineSpacing -> object : LineHeightSpan {
                override fun chooseHeight(
                    text: CharSequence,
                    start: Int,
                    end: Int,
                    spanstartv: Int,
                    lineHeight: Int,
                    fm: Paint.FontMetricsInt?
                ) {
                    val fontMetrics = fm ?: return
                    val halfLineSpacing = (spacing * 0.5f).spToPixel(context).roundToInt()
                    fontMetrics.descent = fontMetrics.bottom + halfLineSpacing
                    fontMetrics.ascent = fontMetrics.top - halfLineSpacing
                    if (paragraphSpacing > 0.0) {
                        if (text[end - 1] == '\n') {
                            fm.descent = fm.descent + paragraphSpacing.spToPixel(context).toInt()
                        }
                    }
                    if (paragraphSpacingBefore > 0.0) {
                        val sizedParagraphSpacing =
                            paragraphSpacingBefore.spToPixel(context).toInt()
                        if (start == 0) {
                            fm.ascent = fm.ascent - sizedParagraphSpacing
                        } else {
                            if (text[start - 1] == '\n') {
                                fm.ascent = fm.ascent - sizedParagraphSpacing
                            }
                        }
                    }
                }
            }
            is StringStyleAttribute.ParagraphStyleAttribute.Alignment -> AlignmentSpan.Standard(
                alignment.alignment(context)
            )
        }

    actual fun create(): StyledString {
        return StyledString(builder, defaultTextStyle)
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.

 */

@file:JvmName("ColorCommonKt")
package com.splendo.kaluga.resources

import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlin.jvm.JvmName

/**
 * Class describing a color
 */
expect class Color

@Serializable(with = ColorSerializer::class)
data class SerializableColor(val color: Color)

val Color.serializable get() = SerializableColor(this)

/**
 * Gets the red value of the color in a range between `0.0` and `1.0`
 */
expect val Color.red: Double
/**
 * Gets the red value of the color in a range between `0` and `255`
 */
expect val Color.redInt: Int
/**
 * Gets the green value of the color in a range between `0.0` and `1.0`
 */
expect val Color.green: Double
/**
 * Gets the green value of the color in a range between `0` and `255`
 */
expect val Color.greenInt: Int
/**
 * Gets the blue value of the color in a range between `0.0` and `1.0`
 */
expect val Color.blue: Double
/**
 * Gets the blue value of the color in a range between `0` and `255`
 */
expect val Color.blueInt: Int
/**
 * Gets the alpha value of the color in a range between `0.0` and `1.0`
 */
expect val Color.alpha: Double
/**
 * Gets the alpha value of the color in a range between `0` and `255`
 */
expect val Color.alphaInt: Int

/**
 * Creates a [Color] using red, green, blue, and (optional) alpha, all ranging between `0.0` and `1.0`.
 * @param red The red color value ranging between `0.0` and `1.0`.
 * @param green The green color value ranging between `0.0` and `1.0`.
 * @param blue The blue color value ranging between `0.0` and `1.0`.
 * @param alpha The alpha color value ranging between `0.0` and `1.0`. Defaults to `1.0`
 * @return The [Color] with the corresponding red, green, blue, and alpha values
 */
expect fun colorFrom(red: Double, green: Double, blue: Double, alpha: Double = 1.0): Color

/**
 * Creates a [Color] using red, green, blue, and (optional) alpha, all ranging between `0` and `255`.
 * @param red The red color value ranging between `0` and `255`.
 * @param green The green color value ranging between `0` and `255`.
 * @param blue The blue color value ranging between `0` and `255`.
 * @param alpha The alpha color value ranging between `0` and `255`. Defaults to `255`
 * @return The [Color] with the corresponding red, green, blue, and alpha values
 */
expect fun colorFrom(redInt: Int, greenInt: Int, blueInt: Int, alphaInt: Int = 255): Color

/**
 * Attempts to parse a given [String] into a [Color].
 * The string should be formatted as either `#AARRGGBB` or `#RRGGBB` for the parsing to succeed.
 * @param hexString The [String] to parse as a [Color]
 * @return The [Color] associated with [hexString] or `null` if improperly formatted.
 */
fun colorFrom(hexString: String): Color? {
    return if (hexString.startsWith('#')) {
        val hexColor = hexString.substring(1).toLong(16)
        when (hexString.length) {
            9 -> {
                val alpha = hexColor ushr 24
                val red = (hexColor shr 16) and 0xFF
                val green = (hexColor shr 8) and 0xFF
                val blue = hexColor and 0xFF
                colorFrom(red.toInt(), green.toInt(), blue.toInt(), alpha.toInt())
            }
            7 -> {
                val red = hexColor ushr 16
                val green = (hexColor shr 8) and 0xFF
                val blue = hexColor and 0xFF
                colorFrom(red.toInt(), green.toInt(), blue.toInt())
            }
            else -> null
        }
    } else {
        null
    }
}

val Color.inverted: Color get() = colorFrom(1.0 - red, 1.0 - green, 1.0 - blue, alpha)
val Color.hexString: String
    get() {
        return "#${alphaInt.toHex(2)}${redInt.toHex(2)}${greenInt.toHex(2)}${blueInt.toHex(2)}"
    }

private fun Int.toHex(minSize: Int): String {
    val hexValue = this.toString(16)
    val prefix = List(minSize - hexValue.length) { "0" }
    return listOf(*prefix.toTypedArray(), hexValue).joinToString("")
}

open class ColorSerializer :
    KSerializer<SerializableColor> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ColorString", PrimitiveKind.STRING)

    override fun serialize(encoder: Encoder, value: SerializableColor) {
        val string = value.color.hexString
        encoder.encodeString(string)
    }

    override fun deserialize(decoder: Decoder): SerializableColor {
        val string = decoder.decodeString()
        return SerializableColor(colorFrom(string)!!)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sqrt

/**
 * Blend Modes as defined by https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdf_reference_archive/blend_modes.pdf
 */
sealed class BlendMode {

    abstract fun blendColor(backdrop: Color, source: Color): Color

    sealed class SeparableBlendMode : BlendMode() {
        override fun blendColor(backdrop: Color, source: Color): Color = colorFrom(
            blendColorChannel(backdrop.red, source.red),
            blendColorChannel(backdrop.green, source.green),
            blendColorChannel(backdrop.blue, source.blue)
        )

        abstract fun blendColorChannel(backdrop: Double, source: Double): Double
    }

    sealed class NonSeparableBlendMode : BlendMode() {
        protected data class UnboundColor(val red: Double, val blue: Double, val green: Double) {
            val lumination: Double get() = 0.3 * red + 0.59 * green + 0.11 * blue
            fun setLumination(lumination: Double): UnboundColor {
                val delta = lumination - this.lumination
                return UnboundColor(red + delta, green + delta, blue + delta)
            }

            val clip: Color
                get() {
                    val lumination = this.lumination
                    val min = minOf(red, green, blue)
                    val max = maxOf(red, green, blue)
                    return when {
                        min < 0.0 -> colorFrom(
                            lumination + (((red - lumination) * lumination) / (lumination - min)),
                            lumination + (((green - lumination) * lumination) / (lumination - min)),
                            lumination + (((blue - lumination) * lumination) / (lumination - min))
                        )
                        max > 1.0 -> colorFrom(
                            lumination + (((red - lumination) * (1.0 - lumination)) / (max - lumination)),
                            lumination + (((green - lumination) * (1.0 - lumination)) / (max - lumination)),
                            lumination + (((blue - lumination) * (1.0 - lumination)) / (max - lumination))
                        )
                        else -> colorFrom(red, green, blue)
                    }
                }
        }

        private val Color.unbounded get() = UnboundColor(red, green, blue)
        protected val Color.lumination get() = unbounded.lumination
        protected val Color.saturation: Double
            get() = maxOf(red, green, blue) - minOf(
                red,
                green,
                blue
            )

        protected fun Color.setLumination(lumination: Double): Color =
            unbounded.setLumination(lumination).clip

        protected fun Color.setSaturation(saturation: Double): Color {
            val keyRed = "red"
            val keyGreen = "green"
            val keyBlue = "blue"
            val channels = mapOf(keyRed to red, keyGreen to green, keyBlue to blue).toList()
                .sortedBy { it.second }.toMutableList()
            if (channels[2].second > channels[0].second) {
                val newMiddle =
                    (((channels[1].second - channels[0].second) * saturation) / (channels[2].second - channels[0].second))
                channels[1] = channels[1].copy(second = newMiddle)
                channels[2] = channels[2].copy(second = saturation)
            } else {
                channels[1] = channels[1].copy(second = 0.0)
                channels[2] = channels[2].copy(second = 0.0)
            }
            channels[0] = channels[0].copy(second = 0.0)
            val channelsMap = channels.toMap()
            return colorFrom(channelsMap[keyRed]!!, channelsMap[keyGreen]!!, channelsMap[keyBlue]!!)
        }
    }

    object Normal : SeparableBlendMode() {
        override fun blendColorChannel(backdrop: Double, source: Double): Double = source
    }

    object Multiply : SeparableBlendMode() {
        override fun blendColorChannel(backdrop: Double, source: Double): Double = backdrop * source
    }

    object Screen : SeparableBlendMode() {
        override fun blendColorChannel(backdrop: Double, source: Double): Double =
            backdrop + source - backdrop * source
    }

    object Overlay : SeparableBlendMode() {
        override fun blendColorChannel(backdrop: Double, source: Double): Double =
            HardLight.blendColorChannel(source, backdrop)
    }

    object Darken : SeparableBlendMode() {
        override fun blendColorChannel(backdrop: Double, source: Double): Double =
            min(backdrop, source)
    }

    object Lighten : SeparableBlendMode() {
        override fun blendColorChannel(backdrop: Double, source: Double): Double =
            max(backdrop, source)
    }

    object HardLight : SeparableBlendMode() {
        override fun blendColorChannel(backdrop: Double, source: Double): Double {
            return if (source <= 0.5) {
                Multiply.blendColorChannel(backdrop, 2.0 * source)
            } else {
                Screen.blendColorChannel(backdrop, 2.0 * source - 1.0)
            }
        }
    }

    object SoftLight : SeparableBlendMode() {
        override fun blendColorChannel(backdrop: Double, source: Double): Double {
            return if (source <= 0.5) {
                backdrop - (1.0 - 2.0 * source) * backdrop * (1.0 - backdrop)
            } else {
                backdrop + (2.0 * source - 1.0) * if (backdrop <= 0.25) {
                    ((16.0 * backdrop - 12.0) * backdrop + 4.0) * backdrop
                } else {
                    sqrt(backdrop)
                }
            }
        }
    }

    object ColorDodge : SeparableBlendMode() {
        override fun blendColorChannel(backdrop: Double, source: Double): Double = when {
            backdrop == 0.0 -> 0.0
            source == 1.0 -> 1.0
            else -> min(1.0, backdrop / (1.0 - source))
        }
    }

    object ColorBurn : SeparableBlendMode() {
        override fun blendColorChannel(backdrop: Double, source: Double): Double = when {
            backdrop == 1.0 -> 1.0
            source == 0.0 -> 0.0
            else -> 1.0 - min(1.0, (1.0 - backdrop) / source)
        }
    }

    object Difference : SeparableBlendMode() {
        override fun blendColorChannel(backdrop: Double, source: Double): Double =
            abs(backdrop - source)
    }

    object Exclusion : SeparableBlendMode() {
        override fun blendColorChannel(backdrop: Double, source: Double): Double {
            return backdrop + source - 2.0 * backdrop * source
        }
    }

    object Hue : NonSeparableBlendMode() {
        override fun blendColor(backdrop: Color, source: Color): Color = source
            .setSaturation(backdrop.saturation)
            .setLumination(backdrop.lumination)
    }

    object Saturation : NonSeparableBlendMode() {
        override fun blendColor(backdrop: Color, source: Color): Color = backdrop
            .setSaturation(source.saturation)
            .setLumination(backdrop.lumination)
    }

    object ColorBlend : NonSeparableBlendMode() {
        override fun blendColor(backdrop: Color, source: Color): Color =
            source.setLumination(backdrop.lumination)
    }

    object Luminosity : NonSeparableBlendMode() {
        override fun blendColor(backdrop: Color, source: Color): Color =
            backdrop.setLumination(source.lumination)
    }
}

/**
 * Blends two colors according to their [BlendMode]
 * For Alpha Blending the W3 standard is applied: https://www.w3.org/TR/compositing-1/#blending
 */
private fun Color.blend(source: Color, mode: BlendMode): Color {
    val alphaCompose = {
        backdropAlpha: Double,
        sourceAlpha: Double,
        compositeAlpha: Double,
        backdropColor: Double,
        sourceColor: Double,
        compositeColor: Double ->
        Double
        (1.0 - sourceAlpha / compositeAlpha) * backdropColor +
            (sourceAlpha / compositeAlpha) * ((1 - backdropAlpha) * sourceColor + backdropAlpha * compositeColor)
    }
    val composite = mode.blendColor(this, source)
    val compositeAlpha = source.alpha + alpha - (source.alpha * alpha)
    return colorFrom(
        alphaCompose(alpha, source.alpha, compositeAlpha, red, source.red, composite.red),
        alphaCompose(alpha, source.alpha, compositeAlpha, green, source.green, composite.green),
        alphaCompose(alpha, source.alpha, compositeAlpha, blue, source.blue, composite.blue),
        compositeAlpha
    )
}

infix fun Color.normal(source: Color) = blend(source, BlendMode.Normal)
infix fun Color.multiply(source: Color) = blend(source, BlendMode.Multiply)
infix fun Color.screen(source: Color) = blend(source, BlendMode.Screen)
infix fun Color.overlay(source: Color) = blend(source, BlendMode.Overlay)
infix fun Color.darken(source: Color) = blend(source, BlendMode.Darken)
infix fun Color.lighten(source: Color) = blend(source, BlendMode.Lighten)
infix fun Color.hardLight(source: Color) = blend(source, BlendMode.HardLight)
infix fun Color.softLight(source: Color) = blend(source, BlendMode.SoftLight)
infix fun Color.dodge(source: Color) = blend(source, BlendMode.ColorDodge)
infix fun Color.burn(source: Color) = blend(source, BlendMode.ColorBurn)
infix fun Color.difference(source: Color) = blend(source, BlendMode.Difference)
infix fun Color.exclude(source: Color) = blend(source, BlendMode.Exclusion)
infix fun Color.hue(source: Color) = blend(source, BlendMode.Hue)
infix fun Color.saturate(source: Color) = blend(source, BlendMode.Saturation)
infix fun Color.luminate(source: Color) = blend(source, BlendMode.Luminosity)
infix fun Color.colorBlend(source: Color) = blend(source, BlendMode.ColorBlend)

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

/**
 * When `true` the application is in Dark Mode.
 */
expect val isInDarkMode: Boolean

/*
 * Copyright 2021 Splendo Consulting B.V. The Netherlands
 *
 *     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.splendo.kaluga.resources

/**
 * All HTML supported colors https://htmlcolorcodes.com/color-names/
 */
object DefaultColors {
    val aliceBlue = colorFrom(240, 248, 255)
    val antiqueWhite = colorFrom(250, 235, 215)
    val aqua = colorFrom(0, 255, 255)
    val aquamarine = colorFrom(127, 255, 212)
    val azure = colorFrom(240, 255, 255)
    val beige = colorFrom(245, 245, 220)
    val bisque = colorFrom(255, 228, 196)
    val black = colorFrom(0, 0, 0)
    val blanchedAlmond = colorFrom(255, 235, 205)
    val blue = colorFrom(0, 0, 255)
    val blueViolet = colorFrom(138, 43, 226)
    val brown = colorFrom(165, 42, 42)
    val burlyWood = colorFrom(222, 184, 135)
    val cadetBlue = colorFrom(95, 158, 160)
    val chartreuse = colorFrom(127, 255, 0)
    val chocolate = colorFrom(210, 105, 30)
    val clear = colorFrom(0.0, 0.0, 0.0, 0.0)
    val coral = colorFrom(255, 127, 80)
    val cornflowerBlue = colorFrom(100, 149, 237)
    val cornsilk = colorFrom(255, 248, 220)
    val crimson = colorFrom(220, 20, 60)
    val cyan = colorFrom(0, 255, 255)
    val darkBlue = colorFrom(0, 0, 139)
    val darkCyan = colorFrom(0, 139, 139)
    val darkGoldenrod = colorFrom(184, 134, 11)
    val darkGray = colorFrom(169, 169, 169)
    val darkGreen = colorFrom(0, 100, 0)
    val darkKhaki = colorFrom(189, 183, 107)
    val darkMagenta = colorFrom(139, 0, 139)
    val darkOliveGreen = colorFrom(85, 107, 47)
    val darkOrange = colorFrom(255, 140, 0)
    val darkOrchid = colorFrom(153, 50, 204)
    val darkRed = colorFrom(139, 0, 0)
    val darkSalmon = colorFrom(233, 150, 122)
    val darkSeaGreen = colorFrom(143, 188, 139)
    val darkSlateBlue = colorFrom(72, 61, 139)
    val darkSlateGray = colorFrom(47, 79, 79)
    val darkTurquoise = colorFrom(0, 206, 209)
    val darkViolet = colorFrom(148, 0, 211)
    val deepPink = colorFrom(255, 20, 147)
    val deepSkyBlue = colorFrom(0, 191, 255)
    val dimGray = colorFrom(105, 105, 105)
    val dodgerBlue = colorFrom(30, 144, 255)
    val fireBrick = colorFrom(178, 34, 34)
    val floralWhite = colorFrom(255, 250, 240)
    val forestGreen = colorFrom(34, 139, 34)
    val fuchsia = colorFrom(255, 0, 255)
    val gainsboro = colorFrom(220, 220, 220)
    val ghostWhite = colorFrom(248, 248, 255)
    val gold = colorFrom(255, 215, 0)
    val goldenrod = colorFrom(218, 165, 32)
    val gray = colorFrom(128, 128, 128)
    val green = colorFrom(0, 128, 0)
    val greenYellow = colorFrom(173, 255, 47)
    val honeyDew = colorFrom(240, 255, 240)
    val hotPink = colorFrom(255, 105, 180)
    val indianRed = colorFrom(205, 92, 92)
    val indigo = colorFrom(75, 0, 130)
    val ivory = colorFrom(255, 255, 240)
    val khaki = colorFrom(240, 230, 140)
    val lavender = colorFrom(230, 230, 250)
    val lavenderBlush = colorFrom(255, 240, 245)
    val lawnGreen = colorFrom(124, 252, 0)
    val lemonChiffon = colorFrom(255, 250, 205)
    val lightBlue = colorFrom(173, 216, 230)
    val lightCoral = colorFrom(240, 128, 128)
    val lightCyan = colorFrom(224, 255, 255)
    val lightGoldenrodYellow = colorFrom(250, 250, 210)
    val lightGray = colorFrom(211, 211, 211)
    val lightGreen = colorFrom(144, 238, 144)
    val lightPink = colorFrom(255, 182, 193)
    val lightSalmon = colorFrom(255, 160, 122)
    val lightSeaGreen = colorFrom(32, 178, 170)
    val lightSkyBlue = colorFrom(135, 206, 250)
    val lightSlateGray = colorFrom(119, 136, 153)
    val lightSteelBlue = colorFrom(176, 196, 222)
    val lightYellow = colorFrom(255, 255, 224)
    val lime = colorFrom(0, 255, 0)
    val limeGreen = colorFrom(50, 205, 50)
    val linen = colorFrom(250, 240, 230)
    val magenta = colorFrom(255, 0, 255)
    val maroon = colorFrom(128, 0, 0)
    val mediumAquamarine = colorFrom(102, 205, 170)
    val mediumBlue = colorFrom(0, 0, 205)
    val mediumOrchid = colorFrom(186, 85, 211)
    val mediumPurple = colorFrom(147, 112, 219)
    val mediumSeaGreen = colorFrom(60, 179, 113)
    val mediumSlateBlue = colorFrom(123, 104, 238)
    val mediumSpringGreen = colorFrom(0, 250, 154)
    val mediumTurquoise = colorFrom(72, 209, 204)
    val mediumVioletRed = colorFrom(199, 21, 133)
    val midnightBlue = colorFrom(25, 25, 112)
    val mintCream = colorFrom(245, 255, 250)
    val mistyRose = colorFrom(255, 228, 225)
    val moccasin = colorFrom(255, 228, 181)
    val navajoWhite = colorFrom(255, 222, 173)
    val navy = colorFrom(0, 0, 128)
    val oldLace = colorFrom(253, 245, 230)
    val olive = colorFrom(128, 128, 0)
    val oliveDrab = colorFrom(107, 142, 35)
    val orange = colorFrom(255, 165, 0)
    val orangeRed = colorFrom(255, 69, 0)
    val orchid = colorFrom(218, 112, 214)
    val paleGoldenrod = colorFrom(238, 232, 170)
    val paleGreen = colorFrom(152, 251, 152)
    val paleTurquoise = colorFrom(175, 238, 238)
    val paleVioletRed = colorFrom(219, 112, 147)
    val papayaWhip = colorFrom(255, 239, 213)
    val peachPuff = colorFrom(255, 218, 185)
    val peru = colorFrom(205, 133, 63)
    val pink = colorFrom(255, 192, 203)
    val plum = colorFrom(221, 160, 221)
    val powderBlue = colorFrom(176, 224, 230)
    val purple = colorFrom(128, 0, 128)
    val rebeccaPurple = colorFrom(102, 51, 153)
    val red = colorFrom(255, 0, 0)
    val rosyBrown = colorFrom(188, 143, 143)
    val royalBlue = colorFrom(65, 105, 225)
    val saddleBrown = colorFrom(139, 69, 19)
    val salmon = colorFrom(250, 128, 114)
    val sandyBrown = colorFrom(244, 164, 96)
    val seaGreen = colorFrom(46, 139, 87)
    val seaShell = colorFrom(255, 245, 238)
    val sienna = colorFrom(160, 82, 45)
    val silver = colorFrom(192, 192, 192)
    val skyBlue = colorFrom(135, 206, 235)
    val slateBlue = colorFrom(106, 90, 205)
    val slateGray = colorFrom(112, 128, 144)
    val snow = colorFrom(255, 250, 250)
    val springGreen = colorFrom(0, 255, 127)
    val steelBlue = colorFrom(70, 130, 180)
    val tan = colorFrom(210, 180, 140)
    val teal = colorFrom(0, 128, 128)
    val thistle = colorFrom(216, 191, 216)
    val tomato = colorFrom(255, 99, 71)
    val turquoise = colorFrom(64, 224, 208)
    val violet = colorFrom(238, 130, 238)
    val wheat = colorFrom(245, 222, 179)
    val white = colorFrom(255, 255, 255)
    val whiteSmoke = colorFrom(245, 245, 245)
    val yellow = colorFrom(255, 255, 0)
    val yellowGreen = colorFrom(154, 205, 50)
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

/**
 * Class describing a font
 */
expect class Font

expect val defaultFont: Font
expect val defaultBoldFont: Font
expect val defaultItalicFont: Font
expect val defaultMonospaceFont: Font

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import kotlinx.serialization.Serializable
import kotlin.math.max
import kotlin.math.min

@Serializable
data class HSLColor(
    val hue: Double,
    val saturation: Double,
    val lightness: Double,
    val alpha: Double = 1.0
)

val Color.hsl: HSLColor get() {
    val max = max(red, max(green, blue))
    val min = min(red, min(green, blue))
    val lightness = (max + min) / 2.0
    val delta = max - min
    return if (delta == 0.0) {
        HSLColor(
            0.0,
            0.0,
            lightness
        )
    } else {
        val saturation = if (lightness > 0.5) delta / (2.0 - max - min) else delta / (max + min)
        val hue = when (max) {
            red -> (green - blue) / delta + if (green < blue) 6.0 else 0.0
            green -> (blue - red) / delta + 2.0
            else -> (red - green) / delta + 4.0
        }
        HSLColor(
            hue / 6.0,
            saturation,
            lightness,
            alpha
        )
    }
}

val HSLColor.color: Color get() {
    return if (saturation == 0.0) {
        colorFrom(lightness, lightness, lightness)
    } else {
        val q = if (lightness < 0.5) lightness * (1 + saturation) else lightness + saturation - (lightness * saturation)
        val p = (2.0 * lightness) - q
        fun hue2Rgb(p: Double, q: Double, t: Double): Double {
            val valueT = when {
                t < 0 -> t + 1.0
                t > 1 -> t - 1.0
                else -> t
            }

            return when {
                valueT < (1.0 / 6.0) -> p + ((q - p) * 6.0 * valueT)
                valueT < 0.5 -> q
                valueT < (2.0 / 3.0) -> p + ((q - p) * ((2.0 / 3.0) - valueT) * 6.0)
                else -> p
            }
        }
        colorFrom(
            hue2Rgb(p, q, hue + (1.0 / 3.0)),
            hue2Rgb(p, q, hue),
            hue2Rgb(p, q, hue - (1.0 / 3.0)),
            alpha
        )
    }
}

fun Color.lightenBy(value: Double): Color = hsl.let {
    return it.copy(lightness = ((1.0 - it.lightness) * value) + it.lightness).color
}

fun Color.darkenBy(value: Double): Color = hsl.let {
    return it.copy(lightness = (it.lightness - (it.lightness) * value)).color
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

/**
 * Class describing an image.
 */
expect class Image

/**
 * Attempts to create a new [Image] that is tinted in a given [Color]
 * @param color The [Color] to use for tinting.
 * @return The tinted [Image] or `null` if tinting could not be applied.
 */
expect fun Image.tinted(color: Color): Image?

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

/**
 * Loads a [String] based on a provided identifier.
 */
interface StringLoader {
    /**
     * Attempts to load the string resource associated with a given identifier. If no match is found, the [defaultValue] will be returned.
     * @param identifier The identifier to find the [String] resource for.
     * @param defaultValue The [String] to return if no match was found for the identifier.
     * @return The associated [String] resources or [defaultValue] if no such resource was found.
     */
    fun loadString(identifier: String, defaultValue: String): String

    /**
     * Attempts to load grammatically correct pluralization of the given string identifier.
     * If no match is found, the [defaultValue] will be returned.
     * @param quantity The value to get pluralized string for.
     * @param identifier The identifier to find the format [String] resource for.
     * @param defaultValue The [String] to return if no match was found for the identifier.
     * @return The associated [String] resources or [defaultValue] if no such resource was found.
     */
    fun loadQuantityString(identifier: String, quantity: Int, defaultValue: String): String
}

/** Default implementation of a [StringLoader]. */
expect class DefaultStringLoader() : StringLoader

/**
 * Loads a [Color] based on a provided identifier.
 */
interface ColorLoader {
    /**
     * Attempts to load the [Color] resource associated with a given identifier. If no match is found, the [defaultValue] will be returned.
     * @param identifier The identifier to find the [Color] resource for.
     * @param defaultValue The [Color] to return if no match was found for the identifier.
     * @return The associated [Color] resources or [defaultValue] if no such resource was found.
     */
    fun loadColor(identifier: String, defaultValue: Color?): Color?
}

/** Default implementation of a [ColorLoader]. */
expect class DefaultColorLoader() : ColorLoader

/**
 * Loads an [Image] based on a provided identifier.
 */
interface ImageLoader {
    /**
     * Attempts to load the [Image] resource associated with a given identifier. If no match is found, the [defaultValue] will be returned.
     * @param identifier The identifier to find the [Image] resource for.
     * @param defaultValue The [Image] to return if no match was found for the identifier.
     * @return The associated [Image] resources or [defaultValue] if no such resource was found.
     */
    fun loadImage(identifier: String, defaultValue: Image?): Image?
}

/** Default implementation of a [ImageLoader]. */
expect class DefaultImageLoader() : ImageLoader

/**
 * Loads a [Font] based on a provided identifier.
 */
interface FontLoader {
    /**
     * Attempts to load the [Font] resource associated with a given identifier. If no match is found, the [defaultValue] will be returned.
     * @param identifier The identifier to find the [Font] resource for.
     * @param defaultValue The [Font] to return if no match was found for the identifier.
     * @return The associated [Font] resources or [defaultValue] if no such resource was found.
     */
    suspend fun loadFont(identifier: String, defaultValue: Font?): Font?
}

/** Default implementation of a [FontLoader]. */
expect class DefaultFontLoader() : FontLoader

/**
 * Treats this string as a resource identifier for a [String] and grabs the associated [String]
 * @param stringLoader The [StringLoader] used for loading the associated [String] resource.
 * @param defaultValue The [String] to return if no match was found for the identifier. Defaults to `this`.
 * @return The [String] associated with the identifier represented by this String, or [defaultValue] if no such [String] could be found.
 */
fun String.localized(
    stringLoader: StringLoader = DefaultStringLoader(),
    defaultValue: String = this
) = stringLoader.loadString(this, defaultValue)

/**
 * Treats this string as a resource identifier for a plural string format [String] and
 * formats given value using associated format [String]
 * @param quantity The [Int] value to be pluralize
 * @param stringLoader The [StringLoader] used for loading the associated [String] resource.
 * @param defaultValue The [String] to return if no match was found for the identifier. Defaults to `this`.
 * @return The [String] associated with the identifier represented by this String, or [defaultValue] if no such [String] could be found.
 */
fun String.quantity(
    quantity: Int,
    stringLoader: StringLoader = DefaultStringLoader(),
    defaultValue: String = this
): String = stringLoader.loadQuantityString(this, quantity, defaultValue)

/**
 * Treats this string as a resource identifier for a [Color] and grabs the associated [Color]
 * @param colorLoader The [ColorLoader] used for loading the associated [Color] resource.
 * @param defaultValue The [Color] to return if no match was found for the identifier. Defaults to `null`.
 * @return The [Color] associated with the identifier represented by this String, or [defaultValue] if no such [Color] could be found.
 */
fun String.asColor(
    colorLoader: ColorLoader = DefaultColorLoader(),
    defaultValue: Color? = null
): Color? = colorLoader.loadColor(this, defaultValue)

/**
 * Treats this string as a resource identifier for a [Color] and grabs the associated [Image]
 * @param imageLoader The [ImageLoader] used for loading the associated [Image] resource.
 * @param defaultValue The [Image] to return if no match was found for the identifier. Defaults to `null`.
 * @return The [Image] associated with the identifier represented by this String, or [defaultValue] if no such [Image] could be found.
 */
fun String.asImage(
    imageLoader: ImageLoader = DefaultImageLoader(),
    defaultValue: Image? = null
): Image? = imageLoader.loadImage(this, defaultValue)

/**
 * Treats this string as a resource identifier for a [Font] and grabs the associated [Font]
 * @param fontLoader The [FontLoader] used for loading the associated [Font] resource.
 * @param defaultValue The [Font] to return if no match was found for the identifier. Defaults to `null`.
 * @return The [Font] associated with the identifier represented by this String, or [defaultValue] if no such [Font] could be found.
 */
suspend fun String.asFont(
    fontLoader: FontLoader = DefaultFontLoader(),
    defaultValue: Font? = null
): Font? = fontLoader.loadFont(this, defaultValue)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.

 */

@file:JvmName("AndroidStyledString")
package com.splendo.kaluga.resources

import com.splendo.kaluga.resources.stylable.TextAlignment
import com.splendo.kaluga.resources.stylable.TextStyle
import kotlin.jvm.JvmName

expect class StyledString {
    val defaultTextStyle: TextStyle
}

expect class StyledStringBuilder {
    class Provider {
        fun provide(string: String, defaultTextStyle: TextStyle): StyledStringBuilder
    }
    fun addStyleAttribute(attribute: StringStyleAttribute, range: IntRange)
    fun create(): StyledString
}

expect val StyledString.rawString: String

fun String.styled(provider: StyledStringBuilder.Provider, defaultTextStyle: TextStyle, vararg attributes: StringStyleAttribute) = styled(
    provider,
    defaultTextStyle,
    *attributes.map<StringStyleAttribute, String.() -> Pair<StringStyleAttribute, IntRange>?> { attribute ->
        { attributeSubstring(this, attribute) }
    }.toTypedArray()
)
fun String.styled(provider: StyledStringBuilder.Provider, defaultTextStyle: TextStyle, vararg attributes: String.() -> Pair<StringStyleAttribute, IntRange>?) = provider.provide(this, defaultTextStyle).apply {
    attributes.forEach { attribute ->
        attribute()?.let {
            addStyleAttribute(it.first, it.second)
        }
    }
}.create()
fun String.attributeSubstring(substring: String, attribute: StringStyleAttribute): Pair<StringStyleAttribute, IntRange>? {
    return indexOf(substring).let {
        if (it >= 0) {
            val range = IntRange(it, it + substring.length - 1)
            Pair(attribute, range)
        } else {
            null
        }
    }
}

sealed class StringStyleAttribute {
    sealed class CharacterStyleAttribute : StringStyleAttribute() {
        data class BackgroundColor(val color: Color) : CharacterStyleAttribute()
        data class ForegroundColor(val color: Color) : CharacterStyleAttribute()
        data class TextStyle(val textStyle: com.splendo.kaluga.resources.stylable.TextStyle) : CharacterStyleAttribute()
        data class Font(val font: com.splendo.kaluga.resources.Font, val size: Float) : CharacterStyleAttribute()
        data class Stroke(val width: Float, val color: Color) : CharacterStyleAttribute()
        data class Shadow(val color: Color, val xOffset: Float, val yOffset: Float, val blurRadius: Float) : CharacterStyleAttribute()
        data class Kerning(val kern: Float) : CharacterStyleAttribute()
        object Strikethrough : CharacterStyleAttribute()
        object Underline : CharacterStyleAttribute()
        object SuperScript : CharacterStyleAttribute()
        object SubScript : CharacterStyleAttribute()
    }

    sealed class ParagraphStyleAttribute : StringStyleAttribute() {
        data class LeadingIndent(val indent: Float, val firstLineIndent: Float = indent) : ParagraphStyleAttribute()
        data class LineSpacing(val spacing: Float, val paragraphSpacing: Float = 0.0f, val paragraphSpacingBefore: Float = 0.0f) : ParagraphStyleAttribute()
        data class Alignment(val alignment: TextAlignment) : ParagraphStyleAttribute()
    }

    data class Link(val url: String) : StringStyleAttribute()
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import kotlinx.cinterop.get
import platform.CoreGraphics.CGColorGetAlpha
import platform.CoreGraphics.CGColorGetComponents
import platform.CoreGraphics.CGFloat
import platform.UIKit.UIColor

actual data class Color(val uiColor: UIColor)

actual val Color.red: Double get() = CGColorGetComponents(uiColor.CGColor)?.get(0)?.toDouble() ?: 0.0
actual val Color.redInt: Int get() = (red * 255.0).toInt()
actual val Color.green: Double get() = CGColorGetComponents(uiColor.CGColor)?.get(1)?.toDouble() ?: 0.0
actual val Color.blueInt: Int get() = (blue * 255.0).toInt()
actual val Color.blue: Double get() = CGColorGetComponents(uiColor.CGColor)?.get(2)?.toDouble() ?: 0.0
actual val Color.greenInt: Int get() = (green * 255.0).toInt()
actual val Color.alpha: Double get() = CGColorGetAlpha(uiColor.CGColor)?.toDouble()
actual val Color.alphaInt: Int get() = (alpha * 255.0).toInt()

actual fun colorFrom(red: Double, green: Double, blue: Double, alpha: Double): Color = Color(UIColor.colorWithRed(red as CGFloat, green as CGFloat, blue as CGFloat, alpha as CGFloat))
actual fun colorFrom(redInt: Int, greenInt: Int, blueInt: Int, alphaInt: Int): Color {
    return colorFrom(redInt.toDouble() / 255.0, greenInt.toDouble() / 255.0, blueInt.toDouble() / 255.0, alphaInt.toDouble() / 255.0)
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import com.splendo.kaluga.base.IOSVersion
import platform.UIKit.UITraitCollection
import platform.UIKit.UIUserInterfaceStyle
import platform.UIKit.currentTraitCollection

actual val isInDarkMode: Boolean get() {
    return if (IOSVersion.systemVersion >= IOSVersion(12))
        UITraitCollection.currentTraitCollection.userInterfaceStyle == UIUserInterfaceStyle.UIUserInterfaceStyleDark
    else
        false
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import platform.UIKit.UIFont
import platform.UIKit.UIFontWeightRegular
import platform.UIKit.labelFontSize

actual typealias Font = UIFont

actual val defaultFont: Font get() = UIFont.systemFontOfSize(UIFont.labelFontSize)
actual val defaultBoldFont: Font get() = UIFont.boldSystemFontOfSize(UIFont.labelFontSize)
actual val defaultItalicFont: Font get() = UIFont.italicSystemFontOfSize(UIFont.labelFontSize)
actual val defaultMonospaceFont: Font get() = UIFont.monospacedSystemFontOfSize(UIFont.labelFontSize, UIFontWeightRegular)

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import platform.UIKit.UIImage
import platform.UIKit.UIImageRenderingMode

actual typealias Image = UIImage

actual fun Image.tinted(color: Color): Image? = this.imageWithTintColor(color.uiColor, UIImageRenderingMode.UIImageRenderingModeAlwaysOriginal)

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import platform.Foundation.NSBundle
import platform.Foundation.NSString
import platform.Foundation.localizedStringWithFormat
import platform.UIKit.UIColor
import platform.UIKit.UIFont
import platform.UIKit.UIImage
import platform.UIKit.UITraitCollection
import platform.UIKit.colorNamed
import platform.UIKit.labelFontSize

actual class DefaultStringLoader(private val bundle: NSBundle, private val table: String?) : StringLoader {
    actual constructor() : this(NSBundle.mainBundle, null)
    override fun loadString(identifier: String, defaultValue: String): String = bundle.localizedStringForKey(identifier, defaultValue, table)
    override fun loadQuantityString(
        identifier: String,
        quantity: Int,
        defaultValue: String
    ): String {
        val format = bundle.localizedStringForKey(identifier, defaultValue, table)
        return NSString.localizedStringWithFormat(format, quantity)
    }
}

actual class DefaultColorLoader(private val bundle: NSBundle, private val traitCollection: UITraitCollection?) : ColorLoader {
    actual constructor() : this(NSBundle.mainBundle, null)
    override fun loadColor(identifier: String, defaultValue: Color?): Color? = UIColor.colorNamed(identifier, bundle, traitCollection)?.let { Color(it) } ?: defaultValue
}

actual class DefaultImageLoader(private val bundle: NSBundle, private val traitCollection: UITraitCollection?) : ImageLoader {
    actual constructor() : this(NSBundle.mainBundle, null)
    override fun loadImage(identifier: String, defaultValue: Image?): Image? = UIImage.imageNamed(identifier, bundle, traitCollection) ?: defaultValue
}

actual class DefaultFontLoader actual constructor() : FontLoader {
    override suspend fun loadFont(identifier: String, defaultValue: Font?): Font? = UIFont.fontWithName(identifier, UIFont.labelFontSize) ?: defaultValue
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import com.splendo.kaluga.resources.stylable.TextStyle
import com.splendo.kaluga.resources.uikit.nsTextAlignment
import kotlinx.cinterop.CValue
import kotlinx.cinterop.alloc
import kotlinx.cinterop.convert
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.nativeHeap
import kotlinx.cinterop.ptr
import kotlinx.cinterop.readValue
import kotlinx.cinterop.useContents
import platform.CoreGraphics.CGFloat
import platform.CoreGraphics.CGSizeMake
import platform.Foundation.NSAttributedString
import platform.Foundation.NSAttributedStringKey
import platform.Foundation.NSMakeRange
import platform.Foundation.NSMutableAttributedString
import platform.Foundation.NSNotFound
import platform.Foundation.NSRange
import platform.Foundation.NSURL
import platform.Foundation.addAttribute
import platform.Foundation.attribute
import platform.Foundation.create
import platform.Foundation.length
import platform.Foundation.removeAttribute
import platform.UIKit.NSMutableParagraphStyle
import platform.UIKit.NSParagraphStyle
import platform.UIKit.NSShadow
import platform.UIKit.NSUnderlineStyleSingle
import platform.darwin.NSUInteger
import kotlin.math.max

actual data class StyledString(val attributeString: NSAttributedString, actual val defaultTextStyle: TextStyle)

actual val StyledString.rawString: String get() = attributeString.string

actual class StyledStringBuilder constructor(string: String, private val defaultTextStyle: TextStyle) {

    actual class Provider {
        actual fun provide(string: String, defaultTextStyle: TextStyle) = StyledStringBuilder(string, defaultTextStyle)
    }

    private val attributedString = NSMutableAttributedString.Companion.create(string)

    actual fun addStyleAttribute(attribute: StringStyleAttribute, range: IntRange) {
        val nsRange = range.nsRange
        when (attribute) {
            is StringStyleAttribute.CharacterStyleAttribute -> {
                attribute.characterAttributes.forEach {
                    attributedString.addAttribute(
                        it.key,
                        it.value,
                        nsRange
                    )
                }
            }
            is StringStyleAttribute.ParagraphStyleAttribute -> {
                attribute.updateParagraphAttribute(nsRange)
            }
            is StringStyleAttribute.Link -> {
                NSURL.Companion.URLWithString(attribute.url)?.let {
                    attributedString.addAttribute("NSLink", it, nsRange)
                }
            }
        }
    }

    private val StringStyleAttribute.CharacterStyleAttribute.characterAttributes: Map<NSAttributedStringKey, Any> get() = when (this) {
        is StringStyleAttribute.CharacterStyleAttribute.ForegroundColor -> mapOf("NSColor" to color.uiColor)
        is StringStyleAttribute.CharacterStyleAttribute.BackgroundColor -> mapOf("NSBackgroundColor" to color.uiColor)
        is StringStyleAttribute.CharacterStyleAttribute.Stroke -> mapOf(
            "NSStrokeColor" to color.uiColor,
            "NSStrokeWidth" to -1.0 * width
        )
        is StringStyleAttribute.CharacterStyleAttribute.SuperScript -> {
            val offset = defaultTextStyle.font.fontWithSize(defaultTextStyle.size.toDouble() as CGFloat).ascender / 2.0
            mapOf("NSBaselineOffset" to offset)
        }
        is StringStyleAttribute.CharacterStyleAttribute.SubScript -> {
            val offset = defaultTextStyle.font.fontWithSize(defaultTextStyle.size.toDouble() as CGFloat).ascender / 2.0
            mapOf("NSBaselineOffset" to (-1.0 * offset))
        }
        is StringStyleAttribute.CharacterStyleAttribute.Underline -> mapOf("NSUnderline" to NSUnderlineStyleSingle)
        is StringStyleAttribute.CharacterStyleAttribute.Strikethrough -> mapOf("NSStrikethrough" to NSUnderlineStyleSingle)
        is StringStyleAttribute.CharacterStyleAttribute.Font -> mapOf("NSFont" to font.fontWithSize(size.toDouble() as CGFloat))
        is StringStyleAttribute.CharacterStyleAttribute.TextStyle -> mapOf(
            "NSFont" to textStyle.font.fontWithSize(textStyle.size.toDouble() as CGFloat),
            "NSColor" to textStyle.color.uiColor
        )
        is StringStyleAttribute.CharacterStyleAttribute.Kerning -> mapOf("NSKern" to kern * defaultTextStyle.size)
        is StringStyleAttribute.CharacterStyleAttribute.Shadow -> mapOf(
            "NSShadow" to NSShadow().apply {
                shadowColor = color.uiColor
                shadowBlurRadius = blurRadius.toDouble() as CGFloat
                shadowOffset = CGSizeMake(xOffset.toDouble() as CGFloat, yOffset.toDouble() as CGFloat)
            }
        )
    }

    private fun StringStyleAttribute.ParagraphStyleAttribute.updateParagraphAttribute(range: CValue<NSRange>) {
        var rangeToProcess = range
        while (rangeToProcess.useContents { length } > 0UL && rangeToProcess.range.last <= range.range.last) {
            memScoped {
                val location = rangeToProcess.useContents { location }
                val matchedRange = nativeHeap.alloc<NSRange>()
                val paragraphStyle = NSMutableParagraphStyle().apply {
                    setParagraphStyle(
                        attributedString.attribute(
                            "NSParagraphStyle",
                            location,
                            matchedRange.ptr,
                            rangeToProcess
                        ) as? NSParagraphStyle ?: NSParagraphStyle.defaultParagraphStyle
                    )
                }

                attributedString.removeAttribute("NSParagraphStyle", matchedRange.readValue())

                when (this@updateParagraphAttribute) {
                    is StringStyleAttribute.ParagraphStyleAttribute.LeadingIndent -> {
                        paragraphStyle.setHeadIndent(indent.toDouble() as CGFloat)
                        paragraphStyle.setFirstLineHeadIndent(firstLineIndent.toDouble() as CGFloat)
                    }
                    is StringStyleAttribute.ParagraphStyleAttribute.LineSpacing -> {
                        paragraphStyle.setLineSpacing(spacing.toDouble() as CGFloat)
                        paragraphStyle.setParagraphSpacing(paragraphSpacing.toDouble() as CGFloat)
                        paragraphStyle.setParagraphSpacingBefore(paragraphSpacingBefore.toDouble() as CGFloat)
                    }
                    is StringStyleAttribute.ParagraphStyleAttribute.Alignment -> {
                        paragraphStyle.setAlignment(alignment.nsTextAlignment)
                    }
                }
                attributedString.addAttribute("NSParagraphStyle", paragraphStyle, matchedRange.readValue())
                rangeToProcess = if (matchedRange.location != NSNotFound.toULong() as NSUInteger) {
                    val nextStart = matchedRange.location + matchedRange.length
                    NSMakeRange(
                        nextStart.convert(),
                        max(
                            0L,
                            range.useContents { this.length.toLong() } - nextStart.toLong()
                        ).convert()
                    )
                } else {
                    NSMakeRange((range.range.last + 1).convert(), 0)
                }
            }
        }
    }

    actual fun create(): StyledString = StyledString(attributedString, defaultTextStyle)
}

val NSAttributedString.urlRanges: List<Pair<NSRange, NSURL>> get() {
    val range = IntRange(0, length.toInt() - 1).nsRange
    var rangeToProcess = range
    val result = mutableListOf<Pair<NSRange, NSURL>>()
    while (rangeToProcess.useContents { length } > 0UL && rangeToProcess.range.last <= range.range.last) {
        memScoped {
            val location = rangeToProcess.useContents { location }
            val matchedRange = nativeHeap.alloc<NSRange>()
            val url = attribute(
                "NSLink",
                location,
                matchedRange.ptr,
                rangeToProcess
            ) as? NSURL
            url?.let {
                result.add(Pair(matchedRange, it))
            }

            rangeToProcess = if (matchedRange.location != NSNotFound.toULong() as NSUInteger) {
                val nextStart = matchedRange.location + matchedRange.length
                NSMakeRange(
                    nextStart.convert(),
                    max(
                        0L,
                        range.useContents { this.length.toLong() } - nextStart.toLong()
                    ).convert()
                )
            } else {
                NSMakeRange((range.range.last + 1).convert(), 0)
            }
        }
    }
    return result
}

private val IntRange.nsRange: CValue<NSRange> get() = NSMakeRange(start.convert(), (endInclusive + 1 - start).convert())
private val CValue<NSRange>.range: IntRange get() = useContents { IntRange(location.toInt(), (location + length).toInt() - 1) }

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

actual typealias Color = Int

actual val Color.red: Double get() = redInt.toDouble() / 255.0
actual val Color.redInt: Int get() = this shr 16 and 0xFF
actual val Color.green: Double get() = greenInt.toDouble() / 255.0
actual val Color.greenInt: Int get() = this shr 8 and 0xFF
actual val Color.blue: Double get() = blueInt.toDouble() / 255.0
actual val Color.blueInt: Int get() = this and 0xFF
actual val Color.alpha: Double get() = alphaInt.toDouble() / 255.0
actual val Color.alphaInt: Int get() = this ushr 24

actual fun colorFrom(red: Double, green: Double, blue: Double, alpha: Double): Color {
    return colorFrom((red * 255.0).toInt(), (green * 255.0).toInt(), (blue * 255.0).toInt(), (alpha * 255.0).toInt())
}

actual fun colorFrom(redInt: Int, greenInt: Int, blueInt: Int, alphaInt: Int): Color = alphaInt shl 24 or (redInt shl 16) or (greenInt shl 8) or blueInt

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

actual val isInDarkMode: Boolean = false

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

actual class Font

actual val defaultFont: Font get() = Font()
actual val defaultBoldFont: Font get() = Font()
actual val defaultItalicFont: Font get() = Font()
actual val defaultMonospaceFont: Font get() = Font()

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

actual class Image

actual fun Image.tinted(color: Color): Image? = null

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import com.splendo.kaluga.base.text.format

actual class DefaultStringLoader(
    private val transformer: (String) -> String?,
    private val formatter: (String, Int) -> String?
) : StringLoader {
    actual constructor() : this({ it }, { format, value -> format.format(value) })

    override fun loadString(identifier: String, defaultValue: String): String =
        transformer(identifier) ?: defaultValue

    override fun loadQuantityString(
        identifier: String,
        quantity: Int,
        defaultValue: String
    ): String = formatter(identifier, quantity) ?: defaultValue
}

actual class DefaultColorLoader(private val transformer: (String) -> Color?) : ColorLoader {
    actual constructor() : this({ null })

    override fun loadColor(identifier: String, defaultValue: Color?): Color? =
        transformer(identifier) ?: defaultValue
}

actual class DefaultImageLoader(private val transformer: (String) -> Image?) : ImageLoader {
    actual constructor() : this({ null })

    override fun loadImage(identifier: String, defaultValue: Image?): Image? =
        transformer(identifier) ?: defaultValue
}

actual class DefaultFontLoader(private val transformer: suspend (String) -> Font?) : FontLoader {
    actual constructor() : this({ null })

    override suspend fun loadFont(identifier: String, defaultValue: Font?): Font? =
        transformer(identifier) ?: defaultValue
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import com.splendo.kaluga.resources.stylable.TextStyle

actual data class StyledString(val string: String, actual val defaultTextStyle: TextStyle, val attributed: List<Pair<StringStyleAttribute, IntRange>>)

actual class StyledStringBuilder constructor(string: String, defaultTextStyle: TextStyle) {

    actual class Provider {
        actual fun provide(string: String, defaultTextStyle: TextStyle) = StyledStringBuilder(string, defaultTextStyle)
    }

    var styledString = StyledString(string, defaultTextStyle, emptyList())
    actual fun addStyleAttribute(attribute: StringStyleAttribute, range: IntRange) {
        styledString = styledString.copy(
            attributed = styledString.attributed.toMutableList().apply {
                add(Pair(attribute, range))
            }
        )
    }
    actual fun create(): StyledString = styledString
}

actual val StyledString.rawString: String get() = string

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

actual typealias Color = Int

actual val Color.red: Double get() = redInt.toDouble() / 255.0
actual val Color.redInt: Int get() = this shr 16 and 0xFF
actual val Color.green: Double get() = greenInt.toDouble() / 255.0
actual val Color.greenInt: Int get() = this shr 8 and 0xFF
actual val Color.blue: Double get() = blueInt.toDouble() / 255.0
actual val Color.blueInt: Int get() = this and 0xFF
actual val Color.alpha: Double get() = alphaInt.toDouble() / 255.0
actual val Color.alphaInt: Int get() = this ushr 24

actual fun colorFrom(red: Double, green: Double, blue: Double, alpha: Double): Color {
    return colorFrom((red * 255.0).toInt(), (green * 255.0).toInt(), (blue * 255.0).toInt(), (alpha * 255.0).toInt())
}

actual fun colorFrom(redInt: Int, greenInt: Int, blueInt: Int, alphaInt: Int): Color = alphaInt shl 24 or (redInt shl 16) or (greenInt shl 8) or blueInt

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

actual val isInDarkMode: Boolean = false

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.

 */
@file:JvmName("JvmFont")
package com.splendo.kaluga.resources

actual class Font

actual val defaultFont: Font get() = Font()
actual val defaultBoldFont: Font get() = Font()
actual val defaultItalicFont: Font get() = Font()
actual val defaultMonospaceFont: Font get() = Font()

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

actual class Image

actual fun Image.tinted(color: Color): Image? = null

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import com.splendo.kaluga.base.text.format

actual class DefaultStringLoader(
    private val transformer: (String) -> String?,
    private val formatter: (String, Int) -> String?
) : StringLoader {
    actual constructor() : this({ it }, { format, value -> format.format(value) })

    override fun loadString(identifier: String, defaultValue: String): String =
        transformer(identifier) ?: defaultValue

    override fun loadQuantityString(
        identifier: String,
        quantity: Int,
        defaultValue: String
    ): String = formatter(identifier, quantity) ?: defaultValue
}

actual class DefaultColorLoader(private val transformer: (String) -> Color?) : ColorLoader {
    actual constructor() : this({ null })

    override fun loadColor(identifier: String, defaultValue: Color?): Color? =
        transformer(identifier) ?: defaultValue
}

actual class DefaultImageLoader(private val transformer: (String) -> Image?) : ImageLoader {
    actual constructor() : this({ null })

    override fun loadImage(identifier: String, defaultValue: Image?): Image? =
        transformer(identifier) ?: defaultValue
}

actual class DefaultFontLoader(private val transformer: suspend (String) -> Font?) : FontLoader {
    actual constructor() : this({ null })

    override suspend fun loadFont(identifier: String, defaultValue: Font?): Font? =
        transformer(identifier) ?: defaultValue
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources

import com.splendo.kaluga.resources.stylable.TextStyle

actual data class StyledString(val string: String, actual val defaultTextStyle: TextStyle, val attributed: List<Pair<StringStyleAttribute, IntRange>>)

actual class StyledStringBuilder constructor(string: String, defaultTextStyle: TextStyle) {

    actual class Provider {
        actual fun provide(string: String, defaultTextStyle: TextStyle) = StyledStringBuilder(string, defaultTextStyle)
    }

    var styledString = StyledString(string, defaultTextStyle, emptyList())
    actual fun addStyleAttribute(attribute: StringStyleAttribute, range: IntRange) {
        styledString = styledString.copy(
            attributed = styledString.attributed.toMutableList().apply {
                add(Pair(attribute, range))
            }
        )
    }
    actual fun create(): StyledString = styledString
}

actual val StyledString.rawString: String get() = string

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.review

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import com.google.android.play.core.ktx.launchReview
import com.google.android.play.core.ktx.requestReview
import com.google.android.play.core.review.ReviewManagerFactory
import com.google.android.play.core.review.testing.FakeReviewManager
import com.splendo.kaluga.architecture.lifecycle.LifecycleManagerObserver
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker
import com.splendo.kaluga.architecture.lifecycle.getOrPutAndRemoveOnDestroyFromCache
import com.splendo.kaluga.architecture.lifecycle.lifecycleManagerObserver
import com.splendo.kaluga.base.ApplicationHolder

actual class ReviewManager(
    private val reviewManager: com.google.android.play.core.review.ReviewManager,
    private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver()
) {

    enum class Type {
        Live,
        Fake;

        internal fun reviewManager(context: Context): com.google.android.play.core.review.ReviewManager = when (this) {
            Live -> ReviewManagerFactory.create(context)
            Fake -> FakeReviewManager(context)
        }
    }

    actual class Builder(
        private val type: Type = Type.Live,
        private val context: Context = ApplicationHolder.applicationContext,
        private val lifecycleManagerObserver: LifecycleManagerObserver = LifecycleManagerObserver()
    ) : LifecycleSubscribableMarker, LifecycleSubscribable by lifecycleManagerObserver {
        actual fun create(): ReviewManager = ReviewManager(type.reviewManager(context), lifecycleManagerObserver)
    }

    actual suspend fun attemptToRequestReview() {
        try {
            val info = reviewManager.requestReview()
            lifecycleManagerObserver.manager?.activity?.let {
                reviewManager.launchReview(it, info)
            }
        } catch (e: com.google.android.play.core.internal.ar) {
            com.splendo.kaluga.logging.error("Failed to retrieve review info.")
        }
    }
}

/**
 * @return A [ReviewManager.Builder] which can be used to show a Review Dialog while this Activity is active.
 *  Will be created if need but only one instance will exist.
 *
 * Warning: Do not attempt to use this builder outside of the lifespan of the Activity.
 * Instead, for example use a [com.splendo.kaluga.architecture.viewmodel.ViewModel],
 * which can automatically track which Activity is active for it.
 *
 */
fun AppCompatActivity.reviewManager(): ReviewManager.Builder = getOrPutAndRemoveOnDestroyFromCache {
    ReviewManager.Builder(lifecycleManagerObserver = lifecycleManagerObserver())
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.review

import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker

/**
 * Manager for requesting the system to show a Review Dialog.
 * This library does not guarantee such a dialog will be shown, as the OS may block such an action.
 */
expect class ReviewManager {

    class Builder : LifecycleSubscribableMarker {
        fun create(): ReviewManager
    }

    /**
     * Attemps to show a dialog that asks the user to submit a review of the app.
     * This method does not guarantee such a dialog will be shown as the OS may block it.
     */
    suspend fun attemptToRequestReview()
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.review

import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker
import platform.StoreKit.SKStoreReviewController

actual class ReviewManager {

    actual class Builder : LifecycleSubscribableMarker {
        actual fun create() = ReviewManager()
    }

    actual suspend fun attemptToRequestReview() {
        SKStoreReviewController.requestReview()
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.review

import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker

actual class ReviewManager {

    actual class Builder : LifecycleSubscribableMarker {
        actual fun create() = ReviewManager()
    }

    actual suspend fun attemptToRequestReview() { }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.review

import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker

actual class ReviewManager {

    actual class Builder : LifecycleSubscribableMarker {
        actual fun create() = ReviewManager()
    }

    actual suspend fun attemptToRequestReview() { }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific

import kotlinx.serialization.Serializable

@Serializable
sealed class PhysicalQuantity {
    @Serializable
    object Acceleration : PhysicalQuantity()
    @Serializable
    object Action : PhysicalQuantity()
    @Serializable
    object AmountOfSubstance : PhysicalQuantity()
    @Serializable
    object Angle : PhysicalQuantity()
    @Serializable
    object AngularAcceleration : PhysicalQuantity()
    @Serializable
    object AngularVelocity : PhysicalQuantity()
    @Serializable
    object Area : PhysicalQuantity()
    @Serializable
    object AreaDensity : PhysicalQuantity()
    @Serializable
    object CatalysticActivity : PhysicalQuantity()
    @Serializable
    object Density : PhysicalQuantity()
    @Serializable
    object DynamicViscosity : PhysicalQuantity()
    @Serializable
    object ElectricCapacitance : PhysicalQuantity()
    @Serializable
    object ElectricCharge : PhysicalQuantity()
    @Serializable
    object ElectricConductance : PhysicalQuantity()
    @Serializable
    object ElectricCurrent : PhysicalQuantity()
    @Serializable
    object ElectricInductance : PhysicalQuantity()
    @Serializable
    object ElectricResistance : PhysicalQuantity()
    @Serializable
    object Energy : PhysicalQuantity()
    @Serializable
    object Force : PhysicalQuantity()
    @Serializable
    object Frequency : PhysicalQuantity()
    @Serializable
    object HeatCapacity : PhysicalQuantity()
    @Serializable
    object Illuminance : PhysicalQuantity()
    @Serializable
    object IonizingRadiationAbsorbedDose : PhysicalQuantity()
    @Serializable
    object IonizingRadiationEquivalentDose : PhysicalQuantity()
    @Serializable
    object LinearMassDensity : PhysicalQuantity()
    @Serializable
    object Jolt : PhysicalQuantity()
    @Serializable
    object Length : PhysicalQuantity()
    @Serializable
    object Luminance : PhysicalQuantity()
    @Serializable
    object LuminousEnergy : PhysicalQuantity()
    @Serializable
    object LuminousExposure : PhysicalQuantity()
    @Serializable
    object LuminousFlux : PhysicalQuantity()
    @Serializable
    object LuminousIntensity : PhysicalQuantity()
    @Serializable
    object MassFlowRate : PhysicalQuantity()
    @Serializable
    object MagneticFlux : PhysicalQuantity()
    @Serializable
    object MagneticInduction : PhysicalQuantity()
    @Serializable
    object Molality : PhysicalQuantity()
    @Serializable
    object Molarity : PhysicalQuantity()
    @Serializable
    object MolarEnergy : PhysicalQuantity()
    @Serializable
    object MolarMass : PhysicalQuantity()
    @Serializable
    object MolarVolume : PhysicalQuantity()
    @Serializable
    object Momentum : PhysicalQuantity()
    @Serializable
    object Power : PhysicalQuantity()
    @Serializable
    object Pressure : PhysicalQuantity()
    @Serializable
    object Radioactivity : PhysicalQuantity()
    @Serializable
    object SolidAngle : PhysicalQuantity()
    @Serializable
    object SpecificEnergy : PhysicalQuantity()
    @Serializable
    object SpecificHeatCapacity : PhysicalQuantity()
    @Serializable
    object SpecificVolume : PhysicalQuantity()
    @Serializable
    object Speed : PhysicalQuantity()
    @Serializable
    object SurfaceTension : PhysicalQuantity()
    @Serializable
    object Temperature : PhysicalQuantity()
    @Serializable
    object ThermalResistance : PhysicalQuantity()
    @Serializable
    object Time : PhysicalQuantity()
    @Serializable
    object Voltage : PhysicalQuantity()
    @Serializable
    object Volume : PhysicalQuantity()
    @Serializable
    object VolumetricFlow : PhysicalQuantity()
    @Serializable
    object VolumetricFlux : PhysicalQuantity()
    @Serializable
    object Weight : PhysicalQuantity()
    @Serializable
    object Yank : PhysicalQuantity()
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.base.utils.toDecimalList
import com.splendo.kaluga.base.utils.toDoubleArray
import com.splendo.kaluga.scientific.unit.ScientificUnit
import com.splendo.kaluga.scientific.unit.convert
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmName

interface ScientificArray<NumberType : Number, Quantity : PhysicalQuantity, Unit : ScientificUnit<Quantity>> : com.splendo.kaluga.base.utils.Serializable {
    operator fun get(index: Int): NumberType
    val size: Int
    val unit: Unit

    operator fun iterator(): Iterator<NumberType>

    val decimalValues: List<Decimal> get() {
        val values = mutableListOf<Decimal>()
        iterator().forEach {
            values.add(it.toDecimal())
        }
        return values
    }
}

interface ByteScientificArray<Quantity : PhysicalQuantity, Unit : ScientificUnit<Quantity>> : ScientificArray<Byte, Quantity, Unit> {
    val values: ByteArray

    override val size: Int
        get() = values.size

    override fun get(index: Int): Byte = values[index]
    override fun iterator(): Iterator<Byte> = values.iterator()
}

interface DoubleScientificArray<Quantity : PhysicalQuantity, Unit : ScientificUnit<Quantity>> : ScientificArray<Double, Quantity, Unit> {
    val values: DoubleArray

    override val size: Int
        get() = values.size

    override fun get(index: Int): Double = values[index]
    override fun iterator(): Iterator<Double> = values.iterator()
}

interface FloatScientificArray<Quantity : PhysicalQuantity, Unit : ScientificUnit<Quantity>> : ScientificArray<Float, Quantity, Unit> {
    val values: FloatArray

    override val size: Int
        get() = values.size

    override fun get(index: Int): Float = values[index]
    override fun iterator(): Iterator<Float> = values.iterator()
}

interface IntScientificArray<Quantity : PhysicalQuantity, Unit : ScientificUnit<Quantity>> : ScientificArray<Int, Quantity, Unit> {
    val values: IntArray

    override val size: Int
        get() = values.size

    override fun get(index: Int): Int = values[index]
    override fun iterator(): Iterator<Int> = values.iterator()
}

interface ShortScientificArray<Quantity : PhysicalQuantity, Unit : ScientificUnit<Quantity>> : ScientificArray<Short, Quantity, Unit> {
    val values: ShortArray

    override val size: Int
        get() = values.size

    override fun get(index: Int): Short = values[index]
    override fun iterator(): Iterator<Short> = values.iterator()
}

interface LongScientificArray<Quantity : PhysicalQuantity, Unit : ScientificUnit<Quantity>> : ScientificArray<Long, Quantity, Unit> {
    val values: LongArray

    override val size: Int
        get() = values.size

    override fun get(index: Int): Long = values[index]
    override fun iterator(): Iterator<Long> = values.iterator()
}

@Serializable
data class DefaultScientificArray<Quantity : PhysicalQuantity, Unit : ScientificUnit<Quantity>>(
    override val values: DoubleArray,
    override val unit: Unit
) : DoubleScientificArray<Quantity, Unit> {
    constructor(value: List<Decimal>, unit: Unit) : this(value.toDoubleArray(), unit)

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null || this::class != other::class) return false

        other as DefaultScientificArray<*, *>

        if (!values.contentEquals(other.values)) return false
        if (unit != other.unit) return false

        return true
    }

    override fun hashCode(): Int {
        var result = values.contentHashCode()
        result = 31 * result + unit.hashCode()
        return result
    }
}

// Creation
@JvmName("scientificArrayFromListOfNumberDefault")
operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > List<Number>.invoke(unit: Unit) = this.toDecimalList()(unit)

@JvmName("scientificArrayFromListOfNumber")
operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    NumberType : Number,
    Array : ScientificArray<NumberType, Quantity, Unit>
    > List<Number>.invoke(unit: Unit, factory: (List<Decimal>, Unit) -> Array) = this.toDecimalList()(unit, factory)

@JvmName("scientificArrayFromListOfDecimalDefault")
operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > List<Decimal>.invoke(unit: Unit) = this(unit, ::DefaultScientificArray)

@JvmName("scientificArrayFromListOfDecimal")
operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    NumberType : Number,
    Array : ScientificArray<NumberType, Quantity, Unit>
    > List<Decimal>.invoke(unit: Unit, factory: (List<Decimal>, Unit) -> Array) = factory(this, unit)

// Group and Split

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>
    > List<ScientificValue<Quantity, Unit>>.toScientificArray(
    unit: TargetUnit,
) = toScientificArray(unit, ::DefaultScientificArray)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>,
    NumberType : Number,
    ArrayType : ScientificArray<NumberType, Quantity, TargetUnit>
    > List<ScientificValue<Quantity, Unit>>.toScientificArray(
    unit: TargetUnit,
    factory: (List<Decimal>, TargetUnit) -> ArrayType
) = factory(map { it.unit.convert(it.decimalValue, unit) }, unit)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    NumberType : Number
    > ScientificArray<NumberType, Quantity, Unit>.split() = split(unit, ::DefaultScientificValue)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    NumberType : Number,
    TargetUnit : ScientificUnit<Quantity>
    > ScientificArray<NumberType, Quantity, Unit>.split(
    targetUnit: TargetUnit
) = split(targetUnit, ::DefaultScientificValue)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    NumberType : Number,
    TargetValue : ScientificValue<Quantity, Unit>
    > ScientificArray<NumberType, Quantity, Unit>.split(
    factory: (Decimal, Unit) -> TargetValue
) = split(unit, factory)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    NumberType : Number,
    TargetUnit : ScientificUnit<Quantity>,
    TargetValue : ScientificValue<Quantity, TargetUnit>
    > ScientificArray<NumberType, Quantity, Unit>.split(
    targetUnit: TargetUnit,
    factory: (Decimal, TargetUnit) -> TargetValue
): List<TargetValue> {
    val target = mutableListOf<TargetValue>()
    iterator().forEach {
        target.add(unit.convert(it, targetUnit)(targetUnit, factory))
    }
    return target
}

// Conversion

fun <
    Quantity : PhysicalQuantity,
    NumberType : Number,
    Unit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>
    > ScientificArray<NumberType, Quantity, Unit>.convert(
    target: TargetUnit
) = convert(target, ::DefaultScientificArray)

fun <
    Quantity : PhysicalQuantity,
    NumberType : Number,
    Unit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>,
    TargetNumberType : Number,
    TargetArray : ScientificArray<TargetNumberType, Quantity, TargetUnit>
    > ScientificArray<NumberType, Quantity, Unit>.convert(
    target: TargetUnit,
    factory: (List<Decimal>, TargetUnit) -> TargetArray
) = map(target, factory) {
    convert(target)
}

fun <
    Quantity : PhysicalQuantity,
    NumberType : Number,
    Unit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>
    > ScientificArray<NumberType, Quantity, Unit>.convertValues(
    target: TargetUnit
): List<Decimal> {
    val values = mutableListOf<Decimal>()
    iterator().forEach {
        values.add(unit.convert(it.toDecimal(), target))
    }
    return values
}

fun <
    Quantity : PhysicalQuantity,
    NumberType : Number,
    Unit : ScientificUnit<Quantity>,
    TargetQuantity : PhysicalQuantity,
    TargetUnit : ScientificUnit<TargetQuantity>
    > ScientificArray<NumberType, Quantity, Unit>.map(
    transform: ScientificValue<Quantity, Unit>.() -> ScientificValue<TargetQuantity, TargetUnit>
) = map(1(unit).transform().unit, ::DefaultScientificArray, transform)

fun <
    Quantity : PhysicalQuantity,
    NumberType : Number,
    Unit : ScientificUnit<Quantity>,
    TargetQuantity : PhysicalQuantity,
    TargetUnit : ScientificUnit<TargetQuantity>,
    TargetNumberType : Number,
    TargetArray : ScientificArray<TargetNumberType, TargetQuantity, TargetUnit>
    > ScientificArray<NumberType, Quantity, Unit>.map(
    factory: (List<Decimal>, TargetUnit) -> TargetArray,
    transform: ScientificValue<Quantity, Unit>.() -> ScientificValue<TargetQuantity, TargetUnit>
) = map(1(unit).transform().unit, factory, transform)

fun <
    Quantity : PhysicalQuantity,
    NumberType : Number,
    Unit : ScientificUnit<Quantity>,
    TargetQuantity : PhysicalQuantity,
    TargetUnit : ScientificUnit<TargetQuantity>
    > ScientificArray<NumberType, Quantity, Unit>.map(
    unit: TargetUnit,
    transform: ScientificValue<Quantity, Unit>.() -> ScientificValue<TargetQuantity, TargetUnit>
) = map(unit, ::DefaultScientificArray, transform)

fun <
    Quantity : PhysicalQuantity,
    NumberType : Number,
    Unit : ScientificUnit<Quantity>,
    TargetQuantity : PhysicalQuantity,
    TargetUnit : ScientificUnit<TargetQuantity>,
    TargetNumberType : Number,
    TargetArray : ScientificArray<TargetNumberType, TargetQuantity, TargetUnit>
    > ScientificArray<NumberType, Quantity, Unit>.map(
    targetUnit: TargetUnit,
    factory: (List<Decimal>, TargetUnit) -> TargetArray,
    transform: ScientificValue<Quantity, Unit>.() -> ScientificValue<TargetQuantity, TargetUnit>
): TargetArray {
    val newValues = mutableListOf<Decimal>()
    iterator().forEach {
        newValues.add(it(unit).transform().decimalValue)
    }
    return factory(newValues, targetUnit)
}

fun <
    LeftQuantity : PhysicalQuantity,
    LeftNumberType : Number,
    LeftUnit : ScientificUnit<LeftQuantity>,
    RightQuantity : PhysicalQuantity,
    RightNumberType : Number,
    RightUnit : ScientificUnit<RightQuantity>,
    TargetQuantity : PhysicalQuantity,
    TargetUnit : ScientificUnit<TargetQuantity>
    > ScientificArray<LeftNumberType, LeftQuantity, LeftUnit>.combine(
    right: ScientificArray<RightNumberType, RightQuantity, RightUnit>,
    transform: ScientificValue<LeftQuantity, LeftUnit>.(ScientificValue<RightQuantity, RightUnit>) -> ScientificValue<TargetQuantity, TargetUnit>
) = combine(right, 1(unit).transform(1(right.unit)).unit, ::DefaultScientificArray, transform)

fun <
    LeftQuantity : PhysicalQuantity,
    LeftNumberType : Number,
    LeftUnit : ScientificUnit<LeftQuantity>,
    RightQuantity : PhysicalQuantity,
    RightNumberType : Number,
    RightUnit : ScientificUnit<RightQuantity>,
    TargetQuantity : PhysicalQuantity,
    TargetUnit : ScientificUnit<TargetQuantity>,
    TargetNumberType : Number,
    TargetArray : ScientificArray<TargetNumberType, TargetQuantity, TargetUnit>
    > ScientificArray<LeftNumberType, LeftQuantity, LeftUnit>.combine(
    right: ScientificArray<RightNumberType, RightQuantity, RightUnit>,
    factory: (List<Decimal>, TargetUnit) -> TargetArray,
    transform: ScientificValue<LeftQuantity, LeftUnit>.(ScientificValue<RightQuantity, RightUnit>) -> ScientificValue<TargetQuantity, TargetUnit>
) = combine(right, 1(unit).transform(1(right.unit)).unit, factory, transform)

fun <
    LeftQuantity : PhysicalQuantity,
    LeftNumberType : Number,
    LeftUnit : ScientificUnit<LeftQuantity>,
    RightQuantity : PhysicalQuantity,
    RightNumberType : Number,
    RightUnit : ScientificUnit<RightQuantity>,
    TargetQuantity : PhysicalQuantity,
    TargetUnit : ScientificUnit<TargetQuantity>
    > ScientificArray<LeftNumberType, LeftQuantity, LeftUnit>.combine(
    right: ScientificArray<RightNumberType, RightQuantity, RightUnit>,
    unit: TargetUnit,
    transform: ScientificValue<LeftQuantity, LeftUnit>.(ScientificValue<RightQuantity, RightUnit>) -> ScientificValue<TargetQuantity, TargetUnit>
) = combine(right, unit, ::DefaultScientificArray, transform)

fun <
    LeftQuantity : PhysicalQuantity,
    LeftNumberType : Number,
    LeftUnit : ScientificUnit<LeftQuantity>,
    RightQuantity : PhysicalQuantity,
    RightNumberType : Number,
    RightUnit : ScientificUnit<RightQuantity>,
    TargetQuantity : PhysicalQuantity,
    TargetUnit : ScientificUnit<TargetQuantity>,
    TargetNumberType : Number,
    TargetArray : ScientificArray<TargetNumberType, TargetQuantity, TargetUnit>
    > ScientificArray<LeftNumberType, LeftQuantity, LeftUnit>.combine(
    right: ScientificArray<RightNumberType, RightQuantity, RightUnit>,
    targetUnit: TargetUnit,
    factory: (List<Decimal>, TargetUnit) -> TargetArray,
    transform: ScientificValue<LeftQuantity, LeftUnit>.(ScientificValue<RightQuantity, RightUnit>) -> ScientificValue<TargetQuantity, TargetUnit>
): TargetArray {
    if (size != right.size)
        throw IndexOutOfBoundsException("Cannot combine Scientific Array of unequal size. Left: $size Right: ${right.size}")
    val newValues = mutableListOf<Decimal>()
    iterator().withIndex().forEach {
        newValues.add(it.value(unit).transform(right[it.index](right.unit)).decimalValue)
    }
    return factory(newValues, targetUnit)
}

infix operator fun <
    Quantity : PhysicalQuantity,
    NumberType : Number,
    Unit : ScientificUnit<Quantity>,
    RightUnit : ScientificUnit<Quantity>
    > ScientificArray<NumberType, Quantity, Unit>.plus(right: ScientificArray<NumberType, Quantity, RightUnit>) = concat(right)

infix operator fun <
    Quantity : PhysicalQuantity,
    NumberType : Number,
    Unit : ScientificUnit<Quantity>,
    RightUnit : ScientificUnit<Quantity>
    > ScientificArray<NumberType, Quantity, Unit>.plus(right: ScientificValue<Quantity, RightUnit>) = concat(listOf(right).toScientificArray(right.unit))

infix operator fun <
    Quantity : PhysicalQuantity,
    NumberType : Number,
    Unit : ScientificUnit<Quantity>,
    RightUnit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, Unit>.plus(right: ScientificArray<NumberType, Quantity, RightUnit>) = listOf(this).toScientificArray(unit).concat(right)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    RightUnit : ScientificUnit<Quantity>,
    > ScientificArray<*, Quantity, Unit>.concat(
    right: ScientificArray<*, Quantity, RightUnit>
) = concat(right, unit)

fun <
    Quantity : PhysicalQuantity,
    NumberType : Number,
    Unit : ScientificUnit<Quantity>,
    Array : ScientificArray<NumberType, Quantity, Unit>
    > ScientificArray<*, Quantity, Unit>.concat(
    right: ScientificArray<*, Quantity, Unit>,
    factory: (List<Decimal>, Unit) -> Array
) = concat(right, unit, factory)

fun <
    Quantity : PhysicalQuantity,
    LeftUnit : ScientificUnit<Quantity>,
    RightUnit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>
    > ScientificArray<*, Quantity, LeftUnit>.concat(
    right: ScientificArray<*, Quantity, RightUnit>,
    targetUnit: TargetUnit
) = concat(right, targetUnit, ::DefaultScientificArray)

fun <
    Quantity : PhysicalQuantity,
    LeftUnit : ScientificUnit<Quantity>,
    RightUnit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>,
    TargetNumberType : Number,
    TargetArray : ScientificArray<TargetNumberType, Quantity, TargetUnit>
    > ScientificArray<*, Quantity, LeftUnit>.concat(
    right: ScientificArray<*, Quantity, RightUnit>,
    targetUnit: TargetUnit,
    factory: (List<Decimal>, TargetUnit) -> TargetArray
) = factory((convertValues(targetUnit) + right.convertValues(targetUnit)), targetUnit)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.RoundingMode
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.minus
import com.splendo.kaluga.base.utils.plus
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.base.utils.toDouble
import com.splendo.kaluga.scientific.unit.ScientificUnit
import com.splendo.kaluga.scientific.unit.convert
import kotlinx.serialization.Serializable

interface ScientificValue<Quantity : PhysicalQuantity, Unit : ScientificUnit<Quantity>> : Comparable<ScientificValue<Quantity, *>>, com.splendo.kaluga.base.utils.Serializable {
    val value: Number
    val unit: Unit

    override fun compareTo(other: ScientificValue<Quantity, *>): Int = unit.toSIUnit(decimalValue).compareTo(other.unit.toSIUnit(other.decimalValue))

    val decimalValue: Decimal get() = value.toDecimal()
}

@Serializable
data class DefaultScientificValue<Quantity : PhysicalQuantity, Unit : ScientificUnit<Quantity>>(
    override val value: Double,
    override val unit: Unit
) : ScientificValue<Quantity, Unit> {
    constructor(value: Decimal, unit: Unit) : this(value.toDouble(), unit)
}

// Creation

operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > Number.invoke(unit: Unit) = this.toDecimal()(unit)
operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    Value : ScientificValue<Quantity, Unit>
    > Number.invoke(unit: Unit, factory: (Decimal, Unit) -> Value) = this.toDecimal()(unit, factory)

operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > Decimal.invoke(unit: Unit) = this(unit, ::DefaultScientificValue)
operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    Value : ScientificValue<Quantity, Unit>
    > Decimal.invoke(unit: Unit, factory: (Decimal, Unit) -> Value) = factory(this, unit)

// Conversion

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>,
    TargetValue : ScientificValue<Quantity, TargetUnit>
    > ScientificValue<Quantity, Unit>.convert(
    target: TargetUnit,
    factory: (Decimal, TargetUnit) -> TargetValue
): TargetValue = factory(convertValue(target), target)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, Unit>.convert(target: TargetUnit) = convert(target, ::DefaultScientificValue)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, Unit>.convertValue(target: TargetUnit): Decimal {
    return unit.convert(decimalValue, target)
}

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>,
    TargetValue : ScientificValue<Quantity, TargetUnit>
    > ScientificValue<Quantity, Unit>.convert(
    target: TargetUnit,
    round: Int,
    roundingMode: RoundingMode = RoundingMode.RoundHalfEven,
    factory: (Decimal, TargetUnit) -> TargetValue
): TargetValue = factory(convertValue(target, round, roundingMode), target)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, Unit>.convert(
    target: TargetUnit,
    round: Int,
    roundingMode: RoundingMode = RoundingMode.RoundHalfEven
) = convert(target, round, roundingMode, ::DefaultScientificValue)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, Unit>.convertValue(
    target: TargetUnit,
    round: Int,
    roundingMode: RoundingMode = RoundingMode.RoundHalfEven
): Decimal {
    return unit.convert(decimalValue, target, round, roundingMode)
}

// Calculation

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, Unit>.plus(factor: Number) = this + factor.toDecimal()

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > Number.plus(unit: ScientificValue<Quantity, Unit>) = toDecimal() + unit

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, Unit>.plus(factor: Decimal) = plus(factor, ::DefaultScientificValue)

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > Decimal.plus(unit: ScientificValue<Quantity, Unit>) = unit.plus(this, ::DefaultScientificValue)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    Value : ScientificValue<Quantity, Unit>
    > ScientificValue<Quantity, Unit>.plus(
    factor: Decimal,
    factory: (Decimal, Unit) -> Value
) = factory(decimalValue + factor, unit)

infix operator fun <
    Quantity : PhysicalQuantity,
    LeftUnit : ScientificUnit<Quantity>,
    RightUnit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, LeftUnit>.plus(right: ScientificValue<Quantity, RightUnit>) = unit.plus(this, right, ::DefaultScientificValue)

fun <
    Quantity : PhysicalQuantity,
    LeftUnit : ScientificUnit<Quantity>,
    RightUnit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>,
    Value : ScientificValue<Quantity, TargetUnit>
    > TargetUnit.plus(
    left: ScientificValue<Quantity, LeftUnit>,
    right: ScientificValue<Quantity, RightUnit>,
    factory: (Decimal, TargetUnit) -> Value
) = byAdding(left, right, factory)

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, Unit>.minus(value: Number) = this - value.toDecimal()

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > Number.minus(unit: ScientificValue<Quantity, Unit>) = toDecimal() - unit

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, Unit>.minus(value: Decimal) = minus(value, ::DefaultScientificValue)

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > Decimal.minus(unit: ScientificValue<Quantity, Unit>) = minus(unit, ::DefaultScientificValue)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    Value : ScientificValue<Quantity, Unit>
    > Decimal.minus(
    value: ScientificValue<Quantity, Unit>,
    factory: (Decimal, Unit) -> Value
) = factory(this - value.decimalValue, value.unit)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    Value : ScientificValue<Quantity, Unit>
    > ScientificValue<Quantity, Unit>.minus(
    value: Decimal,
    factory: (Decimal, Unit) -> Value
) = factory(decimalValue - value, unit)

infix operator fun <
    Quantity : PhysicalQuantity,
    LeftUnit : ScientificUnit<Quantity>,
    RightUnit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, LeftUnit>.minus(right: ScientificValue<Quantity, RightUnit>) = unit.minus(this, right, ::DefaultScientificValue)

fun <
    Quantity : PhysicalQuantity,
    LeftUnit : ScientificUnit<Quantity>,
    RightUnit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>,
    Value : ScientificValue<Quantity, TargetUnit>
    > TargetUnit.minus(
    left: ScientificValue<Quantity, LeftUnit>,
    right: ScientificValue<Quantity, RightUnit>,
    factory: (Decimal, TargetUnit) -> Value
) = bySubtracting(left, right, factory)

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, Unit>.times(factor: Number) = this * factor.toDecimal()

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > Number.times(unit: ScientificValue<Quantity, Unit>) = toDecimal() * unit

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, Unit>.times(factor: Decimal) = times(factor, ::DefaultScientificValue)

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > Decimal.times(unit: ScientificValue<Quantity, Unit>) = unit.times(this, ::DefaultScientificValue)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    Value : ScientificValue<Quantity, Unit>
    > ScientificValue<Quantity, Unit>.times(
    factor: Decimal,
    factory: (Decimal, Unit) -> Value
) = factory(decimalValue * factor, unit)

infix operator fun <
    Quantity : PhysicalQuantity,
    LeftUnit : ScientificUnit<Quantity>,
    RightUnit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, LeftUnit>.times(right: ScientificValue<Quantity, RightUnit>) = unit.times(this, right, ::DefaultScientificValue)

fun <
    Quantity : PhysicalQuantity,
    LeftUnit : ScientificUnit<Quantity>,
    RightUnit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>,
    Value : ScientificValue<Quantity, TargetUnit>
    > TargetUnit.times(
    left: ScientificValue<Quantity, LeftUnit>,
    right: ScientificValue<Quantity, RightUnit>,
    factory: (Decimal, TargetUnit) -> Value
) = byMultiplying(left, right, factory)

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, Unit>.div(factor: Number) = this / factor.toDecimal()

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > Number.div(unit: ScientificValue<Quantity, Unit>) = toDecimal() / unit

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, Unit>.div(factor: Decimal) = div(factor, ::DefaultScientificValue)

infix operator fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>
    > Decimal.div(unit: ScientificValue<Quantity, Unit>) = div(unit, ::DefaultScientificValue)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    Value : ScientificValue<Quantity, Unit>
    > Decimal.div(
    factor: ScientificValue<Quantity, Unit>,
    factory: (Decimal, Unit) -> Value
) = factory(this / factor.decimalValue, factor.unit)

fun <
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    Value : ScientificValue<Quantity, Unit>
    > ScientificValue<Quantity, Unit>.div(
    factor: Decimal,
    factory: (Decimal, Unit) -> Value
) = factory(decimalValue / factor, unit)

infix operator fun <
    Quantity : PhysicalQuantity,
    LeftUnit : ScientificUnit<Quantity>,
    RightUnit : ScientificUnit<Quantity>
    > ScientificValue<Quantity, LeftUnit>.div(right: ScientificValue<Quantity, RightUnit>) = unit.div(this, right, ::DefaultScientificValue)

fun <
    Quantity : PhysicalQuantity,
    LeftUnit : ScientificUnit<Quantity>,
    RightUnit : ScientificUnit<Quantity>,
    TargetUnit : ScientificUnit<Quantity>,
    Value : ScientificValue<Quantity, TargetUnit>
    > TargetUnit.div(
    left: ScientificValue<Quantity, LeftUnit>,
    right: ScientificValue<Quantity, RightUnit>,
    factory: (Decimal, TargetUnit) -> Value
) = byDividing(left, right, factory)

internal fun <
    TargetQuantity : PhysicalQuantity,
    TargetUnit : ScientificUnit<TargetQuantity>,
    Value : ScientificValue<TargetQuantity, TargetUnit>,
    LeftQuantity : PhysicalQuantity,
    LeftUnit : ScientificUnit<LeftQuantity>,
    RightQuantity : PhysicalQuantity,
    RightUnit : ScientificUnit<RightQuantity>
    > TargetUnit.byAdding(
    left: ScientificValue<LeftQuantity, LeftUnit>,
    right: ScientificValue<RightQuantity, RightUnit>,
    factory: (Decimal, TargetUnit) -> Value
) = fromSIUnit(left.unit.toSIUnit(left.decimalValue) + right.unit.toSIUnit(right.decimalValue))(this, factory)

internal fun <
    TargetQuantity : PhysicalQuantity,
    TargetUnit : ScientificUnit<TargetQuantity>,
    Value : ScientificValue<TargetQuantity, TargetUnit>,
    LeftQuantity : PhysicalQuantity,
    LeftUnit : ScientificUnit<LeftQuantity>,
    RightQuantity : PhysicalQuantity,
    RightUnit : ScientificUnit<RightQuantity>
    > TargetUnit.bySubtracting(
    left: ScientificValue<LeftQuantity, LeftUnit>,
    right: ScientificValue<RightQuantity, RightUnit>,
    factory: (Decimal, TargetUnit) -> Value
) = fromSIUnit(left.unit.toSIUnit(left.decimalValue) - right.unit.toSIUnit(right.decimalValue))(this, factory)

internal fun <
    TargetQuantity : PhysicalQuantity,
    Unit : ScientificUnit<TargetQuantity>,
    Value : ScientificValue<TargetQuantity, Unit>,
    NominatorQuantity : PhysicalQuantity,
    NominatorUnit : ScientificUnit<NominatorQuantity>,
    DividerQuantity : PhysicalQuantity,
    DividerUnit : ScientificUnit<DividerQuantity>
    > Unit.byDividing(
    nominator: ScientificValue<NominatorQuantity, NominatorUnit>,
    divider: ScientificValue<DividerQuantity, DividerUnit>,
    factory: (Decimal, Unit) -> Value
) = fromSIUnit(nominator.unit.toSIUnit(nominator.decimalValue) / divider.unit.toSIUnit(divider.decimalValue))(this, factory)

internal fun <
    TargetQuantity : PhysicalQuantity,
    TargetUnit : ScientificUnit<TargetQuantity>,
    Value : ScientificValue<TargetQuantity, TargetUnit>,
    LeftQuantity : PhysicalQuantity,
    LeftUnit : ScientificUnit<LeftQuantity>,
    RightQuantity : PhysicalQuantity,
    RightUnit : ScientificUnit<RightQuantity>
    > TargetUnit.byMultiplying(
    left: ScientificValue<LeftQuantity, LeftUnit>,
    right: ScientificValue<RightQuantity, RightUnit>,
    factory: (Decimal, TargetUnit) -> Value
) = fromSIUnit(left.unit.toSIUnit(left.decimalValue) * right.unit.toSIUnit(right.decimalValue))(this, factory)

internal fun <
    InverseQuantity : PhysicalQuantity,
    InverseUnit : ScientificUnit<InverseQuantity>,
    Quantity : PhysicalQuantity,
    Unit : ScientificUnit<Quantity>,
    Value : ScientificValue<Quantity, Unit>
    > Unit.byInverting(
    inverse: ScientificValue<InverseQuantity, InverseUnit>,
    factory: (Decimal, Unit) -> Value
) = fromSIUnit(1.0.toDecimal() / inverse.unit.toSIUnit(inverse.decimalValue))(this, factory)

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.storage

import android.Manifest
import android.content.Context
import com.splendo.kaluga.permissions.AndroidPermissionsManager
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class StoragePermissionManager(
    context: Context,
    actual val storage: StoragePermission,
    stateRepo: StoragePermissionStateRepo
) : PermissionManager<StoragePermission>(stateRepo) {

    private val permissionsManager = AndroidPermissionsManager(context, this, if (storage.allowWrite) arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE) else arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE))

    override suspend fun requestPermission() {
        permissionsManager.requestPermissions()
    }

    override suspend fun initializeState(): PermissionState<StoragePermission> {
        return when {
            permissionsManager.hasPermissions -> PermissionState.Allowed()
            else -> PermissionState.Denied.Requestable()
        }
    }

    override suspend fun startMonitoring(interval: Long) {
        permissionsManager.startMonitoring(interval)
    }

    override suspend fun stopMonitoring() {
        permissionsManager.stopMonitoring()
    }
}

actual class StoragePermissionManagerBuilder actual constructor(private val context: PermissionContext) : BaseStoragePermissionManagerBuilder {

    override fun create(storage: StoragePermission, repo: StoragePermissionStateRepo): PermissionManager<StoragePermission> {
        return StoragePermissionManager(context.context, storage, repo)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.storage

import com.splendo.kaluga.permissions.Permission
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.defaultPermissionContext

/**
 * Permission to access the users device storage.
 * On iOS this corresponds to the Photos permission
 * @param allowWrite If `true` writing to the storage is permitted
 */
data class StoragePermission(val allowWrite: Boolean = false) : Permission()

fun PermissionsBuilder.registerStoragePermission() =
    registerStoragePermissionBuilder(context).also { builder ->
        registerPermissionStateRepoBuilder(StoragePermission::class) { permission, coroutineContext ->
            StoragePermissionStateRepo(permission as StoragePermission, builder as BaseStoragePermissionManagerBuilder, coroutineContext)
        }
    }

internal fun PermissionsBuilder.registerStoragePermissionBuilder(context: PermissionContext = defaultPermissionContext): StoragePermissionManagerBuilder = register(
    builder = StoragePermissionManagerBuilder(context),
    permission = StoragePermission::class
)

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.storage

import com.splendo.kaluga.permissions.BasePermissionsBuilder
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionStateRepo
import com.splendo.kaluga.permissions.defaultPermissionContext
import kotlin.coroutines.CoroutineContext

/**
 * A [PermissionManager] for managing [StoragePermission]
 */
expect class StoragePermissionManager : PermissionManager<StoragePermission> {
    /**
     * The [StoragePermission] managed by this manager.
     */
    val storage: StoragePermission
}

/**
 * Alias for [StoragePermissionManager]
 */
typealias PhotosPermissionManager = StoragePermissionManager

interface BaseStoragePermissionManagerBuilder : BasePermissionsBuilder {
    /**
     * Creates a [StoragePermissionManager]
     * @param repo The [StoragePermissionStateRepo] associated with the [StoragePermission]
     */
    fun create(storage: StoragePermission, repo: StoragePermissionStateRepo): PermissionManager<StoragePermission>
}

/**
 * A builder for creating a [StoragePermissionManager]
 */
expect class StoragePermissionManagerBuilder(context: PermissionContext = defaultPermissionContext) : BaseStoragePermissionManagerBuilder

/**
 * Alias for [StoragePermissionManagerBuilder]
 */
typealias PhotosPermissionManagerBuilder = StoragePermissionManagerBuilder

/**
 * A [PermissionStateRepo] for [StoragePermission]
 * @param builder The [StoragePermissionManagerBuilder] for creating the [StoragePermissionManager] associated with the permission
 * @param coroutineContext The [CoroutineContext] to run the state machine on.
 */
class StoragePermissionStateRepo(storage: StoragePermission, builder: BaseStoragePermissionManagerBuilder, coroutineContext: CoroutineContext) : PermissionStateRepo<StoragePermission>(coroutineContext = coroutineContext) {

    override val permissionManager: PermissionManager<StoragePermission> = builder.create(storage, this)
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.storage

import com.splendo.kaluga.base.mainContinuation
import com.splendo.kaluga.logging.error
import com.splendo.kaluga.permissions.IOSPermissionsHelper
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionRefreshScheduler
import com.splendo.kaluga.permissions.PermissionState
import platform.Foundation.NSBundle
import platform.Photos.PHAuthorizationStatus
import platform.Photos.PHAuthorizationStatusAuthorized
import platform.Photos.PHAuthorizationStatusDenied
import platform.Photos.PHAuthorizationStatusNotDetermined
import platform.Photos.PHAuthorizationStatusRestricted
import platform.Photos.PHPhotoLibrary

const val NSPhotoLibraryUsageDescription = "NSPhotoLibraryUsageDescription"

actual class StoragePermissionManager(
    private val bundle: NSBundle,
    actual val storage: StoragePermission,
    stateRepo: StoragePermissionStateRepo
) : PermissionManager<StoragePermission>(stateRepo) {

    private val authorizationStatus = suspend {
        PHPhotoLibrary.authorizationStatus().toAuthorizationStatus()
    }
    private var timerHelper: PermissionRefreshScheduler<StoragePermission> = PermissionRefreshScheduler(this, authorizationStatus)

    override suspend fun requestPermission() {
        if (IOSPermissionsHelper.missingDeclarationsInPList(bundle, NSPhotoLibraryUsageDescription).isEmpty()) {
            timerHelper.isWaiting.value = true
            PHPhotoLibrary.requestAuthorization(
                mainContinuation { status ->
                    timerHelper.isWaiting.value = false
                    IOSPermissionsHelper.handleAuthorizationStatus(status.toAuthorizationStatus(), this)
                }
            )
        } else {
            revokePermission(true)
        }
    }

    override suspend fun initializeState(): PermissionState<StoragePermission> {
        return IOSPermissionsHelper.getPermissionState(authorizationStatus())
    }

    override suspend fun startMonitoring(interval: Long) {
        timerHelper.startMonitoring(interval)
    }

    override suspend fun stopMonitoring() {
        timerHelper.stopMonitoring()
    }
}

actual class StoragePermissionManagerBuilder actual constructor(private val context: PermissionContext) : BaseStoragePermissionManagerBuilder {

    override fun create(storage: StoragePermission, repo: StoragePermissionStateRepo): PermissionManager<StoragePermission> {
        return StoragePermissionManager(context, storage, repo)
    }
}

private fun PHAuthorizationStatus.toAuthorizationStatus(): IOSPermissionsHelper.AuthorizationStatus {
    return when (this) {
        PHAuthorizationStatusAuthorized -> IOSPermissionsHelper.AuthorizationStatus.Authorized
        PHAuthorizationStatusDenied -> IOSPermissionsHelper.AuthorizationStatus.Denied
        PHAuthorizationStatusRestricted -> IOSPermissionsHelper.AuthorizationStatus.Restricted
        PHAuthorizationStatusNotDetermined -> IOSPermissionsHelper.AuthorizationStatus.NotDetermined
        else -> {
            error(
                "StoragePermissionManager",
                "Unknown StorageManagerAuthorization status={$this}"
            )
            IOSPermissionsHelper.AuthorizationStatus.NotDetermined
        }
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.storage

import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState

actual class StoragePermissionManager(actual val storage: StoragePermission, repo: StoragePermissionStateRepo) : PermissionManager<StoragePermission>(repo) {

    override suspend fun requestPermission() {
        TODO("not implemented")
    }

    override suspend fun initializeState(): PermissionState<StoragePermission> {
        TODO("not implemented")
    }

    override suspend fun startMonitoring(interval: Long) {
        TODO("not implemented")
    }

    override suspend fun stopMonitoring() {
        TODO("not implemented")
    }
}

actual class StoragePermissionManagerBuilder actual constructor(context: PermissionContext) : BaseStoragePermissionManagerBuilder {

    override fun create(storage: StoragePermission, repo: StoragePermissionStateRepo): PermissionManager<StoragePermission> {
        return StoragePermissionManager(storage, repo)
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.test

import kotlin.test.AfterTest
import kotlin.test.BeforeTest

expect open class BaseTest() {
    @BeforeTest
    open fun beforeTest()

    @AfterTest
    open fun afterTest()
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.test

import co.touchlab.stately.concurrency.AtomicLong
import co.touchlab.stately.ensureNeverFrozen
import co.touchlab.stately.freeze
import com.splendo.kaluga.base.runBlocking
import com.splendo.kaluga.base.utils.EmptyCompletableDeferred
import com.splendo.kaluga.base.utils.complete
import com.splendo.kaluga.logging.debug
import com.splendo.kaluga.logging.e
import com.splendo.kaluga.logging.warn
import com.splendo.kaluga.test.UIThreadTest.EmptyTestContext
import com.splendo.kaluga.test.UIThreadTest.TestContext
import com.splendo.kaluga.test.koin.KoinUIThreadTest
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect
import kotlin.native.concurrent.SharedImmutable
import kotlin.native.concurrent.ThreadLocal
import kotlin.test.AfterTest

typealias TestBlock<TC, T> = suspend TC.(T) -> Unit
typealias ActionBlock = suspend() -> Unit
typealias ScopeActionBlock<TC> = suspend TC.() -> Unit

typealias FlowTestBlockWithContext<TC, T, F> = suspend BaseFlowTest<TC, T, F>.(F) -> Unit

typealias FlowTestBlock<T, F> = suspend FlowTest<T, F>.(F) -> Unit

abstract class FlowableTest<T> : BaseTest() {

    fun testWithFlow(block: FlowTestBlock<T, MutableSharedFlow<T>>) = runBlocking {
        object : FlowTest<T, MutableSharedFlow<T>>(this@runBlocking) {
            override val flow = suspend { mutableSharedFlow() }
        }.testWithFlow(block)
    }
    abstract fun mutableSharedFlow(): MutableSharedFlow<T>
}

abstract class SimpleFlowTest<T>(scope: CoroutineScope = MainScope()) : FlowTest<T, Flow<T>>(scope)

abstract class FlowTest<T, F : Flow<T>>(scope: CoroutineScope = MainScope()) : BaseFlowTest<EmptyTestContext, T, F>(scope) {
    override val createTestContext: suspend (CoroutineScope) -> EmptyTestContext = { EmptyTestContext.INSTANCE }

    override val flowFromTestContext: suspend EmptyTestContext.() -> F =
        { flow() }

    abstract val flow: suspend () -> F

    fun testWithFlow(block: FlowTestBlock<T, F>) =
        super.testWithFlowAndTestContext(createFlowInMainScope = false, retainContextAfterTest = false) {
            block(this@FlowTest, it)
        }
}

abstract class BaseKoinFlowTest<TC : KoinUIThreadTest.KoinTestContext, T, F : Flow<T>> : BaseFlowTest<TC, T, F>()

/*
Context for each tests needs to be created and kept on the main thread for iOS.

Since the class itself is created in the test thread
*/

@ThreadLocal // thread local on native, global on non-native, but still accessed from only the main thread.
val contextMap = mutableMapOf<Long, TestContext>()

private suspend fun <TC : TestContext> testContext(cookie: Long, testContext: suspend () -> TC): TC {
    if (!contextMap.containsKey(cookie))
        contextMap[cookie] = testContext()
    return contextMap[cookie] as TC
}

@SharedImmutable
private val cookieTin = AtomicLong(0L)

abstract class BaseFlowTest<TC : TestContext, T, F : Flow<T>>(val scope: CoroutineScope = MainScope()) : UIThreadTest<TC>(), CoroutineScope by scope {

    // used for thread local map access to store the testContext
    private val cookie = cookieTin.incrementAndGet()

    init {
        ensureNeverFrozen()
    }

    abstract val flowFromTestContext: suspend TC.() -> F

    open val filter: (Flow<T>) -> Flow<T> = { it }

    private val tests: MutableList<EmptyCompletableDeferred> = mutableListOf()

    var job: Job? = null

    private lateinit var testChannel: Channel<Pair<TestBlock<TC, T>, EmptyCompletableDeferred>>

    @AfterTest
    override fun afterTest() {
        runBlocking {
            disposeContext(cookie)
        }
        super.afterTest()
    }

    suspend fun resetFlow() {
        awaitTestBlocks() // get the final test blocks that were executed and check for exceptions
        debug("job: $job")
        job?.cancel()
        debug("Ending flow, job canceled")

        firstTestBlock = true
        testChannel.close()

        debug("test channel closed")

        tests.clear()
        testChannel = Channel(Channel.UNLIMITED)
    }

    protected val waitForTestToSucceed = 6000L * 10

    private suspend fun awaitTestBlocks() {

        tests.removeAll { !it.isActive }

        if (tests.size == 0) {
            debug("await all test blocks, but none found, skip waiting")
            return
        }

        withTimeout(waitForTestToSucceed) {
            try {
                debug("await all test blocks (${tests.size}), give it $waitForTestToSucceed milliseconds")
                tests.awaitAll()
            } finally {
                tests.removeAll { !it.isActive }
            }
        }
    }

    private var lateflow: F? = null

    fun testWithFlowAndTestContext(
        createFlowInMainScope: Boolean = true,
        retainContextAfterTest: Boolean = false,
        blockWithContext: FlowTestBlockWithContext<TC, T, F>
    ) {

        runBlocking {
            try {
                testChannel = Channel(Channel.UNLIMITED)

                // startFlow is only called when the first test block is offered

                val flow = flowFromTestContext

                val cookie = cookie
                val scope = scope

                val createTestContext = createTestContext

                val f =
                    if (createFlowInMainScope) {
                        flow.freeze()
                        scope.freeze()
                        withContext(Dispatchers.Main.immediate) {
                            (flow(testContext(cookie) { createTestContext(scope) })).freeze()
                        }
                    } else {
                        createTestContext.freeze()
                        flow(
                            withContext(Dispatchers.Main.immediate) {
                                (testContext(cookie) { createTestContext(scope) }).freeze()
                            }
                        )
                    }

                lateflow = f
                blockWithContext(f)
                resetFlow()
            } finally {
                if (!retainContextAfterTest) {
                    disposeContext(cookie)
                }
            }
        }
    }

    @Suppress("SuspendFunctionOnCoroutineScope")
    private suspend fun startFlow(flow: F) {
        this.ensureNeverFrozen()
        debug("launch flow scope...")
        val testChannel = testChannel
        val started = EmptyCompletableDeferred()
        val filter = filter
        val scope = scope
        val createTestContext = createTestContext
        val cookie = cookie
        createTestContext.freeze()
        try {
            job = launch(Dispatchers.Main.immediate) {
                started.complete()
                val testContext = testContext(cookie) { createTestContext(scope) }
                debug("main scope launched, about to flow, test channel ${if (testChannel.isEmpty) "" else "not "}empty ")
                filter(flow).collect { value ->
                    debug("in flow received [$value], test channel ${if (testChannel.isEmpty) "" else "not "}empty \"")
                    val test = testChannel.receive()
                    debug("received test block $test")
                    try {
                        test.first(testContext, value)
                        debug("ran test block $test")
                        test.second.complete(Unit)
                        debug("completed $test")
                    } catch (t: Throwable) {
                        warn(throwable = t) { "Exception when testing... $t cause: ${t.cause}" }
                        try {
                            test.second.completeExceptionally(t)
                            debug("completed exceptionally $test")
                        } catch (t: Throwable) {
                            e(throwable = t) { "exception in completing completable" }
                        }
                    }
                    debug("handeling value completed [$value]")
                }
                debug("flow collect completed")
            }
        } catch (t: Throwable) {
            e(throwable = t) { "error launching" }
            throw t
        }
        debug("wait for main thread to be launched in $job")
        started.await()
        debug("waited for main thread to be launched")
    }

    suspend fun mainAction(action: ScopeActionBlock<TC>) {
        debug("start mainAction")
        awaitTestBlocks()
        val cookie = cookie
        val createTestContext = createTestContext
        val scope = scope
        withContext(Dispatchers.Main.immediate) {
            debug("in main scope for mainAction")
            action(testContext(cookie) { createTestContext(scope) })
        }
        debug("did mainAction")
    }

    suspend fun action(action: ActionBlock) {
        debug("start action")
        awaitTestBlocks()
        action()
        debug("did action")
    }

    var firstTestBlock = true

    suspend fun test(skip: Int = 0, test: TestBlock<TC, T>) {
        test.freeze()
        if (firstTestBlock) {
            firstTestBlock = false
            tests.ensureNeverFrozen()
            debug("first test offered, starting collection")
            require(lateflow != null) { "Only use test from inside `testWith` methods" }
            startFlow(lateflow!!)
        }
        repeat(skip) {
            test {}
        }
        val completable = EmptyCompletableDeferred()
        tests.add(completable)
        debug("${tests.size} in collection (including this one), offering")
        testChannel.trySend(Pair(test, completable)).isSuccess
    }

    private suspend fun disposeContext(cookie: Long) {
        withContext(Dispatchers.Main.immediate) {
            val testContext = contextMap.remove(cookie)
            testContext?.dispose()
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test

import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.base.runBlocking
import com.splendo.kaluga.base.utils.EmptyCompletableDeferred
import com.splendo.kaluga.base.utils.complete
import com.splendo.kaluga.permissions.Permission
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionState
import com.splendo.kaluga.permissions.PermissionStateRepo
import kotlinx.coroutines.CompletableDeferred

class MockPermissionStateRepo<P : Permission> : PermissionStateRepo<P>() {

    override val permissionManager = MockPermissionManager(this)
}

class MockPermissionManager<P : Permission>(private val permissionRepo: PermissionStateRepo<P>) : PermissionManager<P>(permissionRepo) {

    private val _currentState: AtomicReference<PermissionState<P>> = AtomicReference(PermissionState.Denied.Requestable())
    var currentState: PermissionState<P>
        get() = _currentState.get()
        set(s) = _currentState.set(s)

    fun setPermissionAllowed() {
        changePermission { grantPermission() }
    }

    fun setPermissionDenied() {
        changePermission { revokePermission(false) }
    }

    fun setPermissionLocked() {
        changePermission { revokePermission(true) }
    }

    private fun changePermission(action: suspend () -> Unit) = runBlocking {
        permissionRepo.useState { action() }
        permissionRepo.useState { currentState = it }
    }

    val hasRequestedPermission = EmptyCompletableDeferred()
    val hasStartedMonitoring = CompletableDeferred<Long>()
    val hasStoppedMonitoring = EmptyCompletableDeferred()

    override suspend fun requestPermission() {
        hasRequestedPermission.complete()
    }

    override suspend fun initializeState(): PermissionState<P> {
        return currentState
    }

    override suspend fun startMonitoring(interval: Long) {
        hasStartedMonitoring.complete(interval)
    }

    override suspend fun stopMonitoring() {
        hasStoppedMonitoring.complete()
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test

import co.touchlab.stately.isFrozen
import kotlin.native.concurrent.SharedImmutable
import kotlin.test.assertFalse
import kotlin.test.assertTrue

@SharedImmutable
private val WARNING = "Checking if a Boolean is frozen, did you accidentally add `.isFrozen` in your assert? If intentional add `allowBoolean = true`."

fun assertFrozen(any: Any, message: String? = null, allowBoolean: Boolean = false) {
    if (!allowBoolean && any is Boolean)
        error(WARNING)
    assertTrue(any.isFrozen, message)
}

fun assertNotFrozen(any: Any, message: String? = null, suppressWarning: Boolean = false) {
    if (!suppressWarning && any is Boolean)
        error(WARNING)
    assertFalse(any.isFrozen, message)
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test

import com.splendo.kaluga.base.runBlocking
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.awaitAll

fun <T> awaitAllBlocking(vararg deferreds: Deferred<T>): List<T> = runBlocking { awaitAll(*deferreds) }

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test

import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.withTimeout
import kotlin.time.Duration

/**
 * Returns a list of flow items captured for [duration]
 * @param duration of capturing
 * @return a list of flow items captured for [duration]
 */
suspend fun <T> Flow<T>.captureFor(duration: Duration): List<T> {
    val output = mutableListOf<T>()

    try {
        withTimeout(duration) {
            collect { output += it }
        }
    } catch (e: TimeoutCancellationException) {
        // ignore
    }
    return output
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test

import co.touchlab.stately.freeze
import com.splendo.kaluga.base.runBlocking
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext

class DeliberateCancellationException(val result: Any?, freeze: Boolean) :
    kotlinx.coroutines.CancellationException("Scope canceled deliberately by testAndCancelScope") {
    init {
        if (freeze) freeze()
    }
}

inline fun <reified T> testBlockingAndCancelScope(
    context: CoroutineContext = EmptyCoroutineContext,
    freezeResult: Boolean = true,
    crossinline block: suspend CoroutineScope.() -> T
): T {

    try {
        return runBlocking(context) {
            block().also { cancel(DeliberateCancellationException(it, freezeResult)) }
        }
    } catch (e: Throwable) {
        if (e is DeliberateCancellationException && e.result is T)
            return e.result
        throw e // not our expected exception
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test

import co.touchlab.stately.ensureNeverFrozen
import co.touchlab.stately.freeze
import com.splendo.kaluga.base.runBlocking
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.yield

open class SimpleUIThreadTest : UIThreadTest<SimpleUIThreadTest.SimpleTestContext>() {
    class SimpleTestContext(coroutineScope: CoroutineScope) : TestContext, CoroutineScope by coroutineScope

    override val createTestContext: suspend (CoroutineScope) -> SimpleTestContext = { SimpleTestContext(it) }
}

/**
 * This class allows a test block to be run on the UI thread inside a custom context
 * that is also created in the UI Thread.
 *
 * While normally iOS tests already run on the UI thread, this prevents the Main dispatcher from working as long as that Thread is blocked,
 * so this class is mostly used in conjunction with the [com.splendo.kaluga.test.mainBackground] entry point.
 *
 * It is useful for Kotlin Native classes that only work when created on the UI thread,
 * as it eases dealing with immutability and allows a shared context.
 *
 */
abstract class UIThreadTest<TC : UIThreadTest.TestContext>(allowFreezing: Boolean = false) : BaseTest() {

    init {
        if (!allowFreezing) ensureNeverFrozen()
    }

    interface TestContext {
        fun dispose() {}
    }

    class EmptyTestContext private constructor() : TestContext {
        companion object {
            val INSTANCE = EmptyTestContext()
        }
    }

    abstract val createTestContext: suspend (scope: CoroutineScope) -> TC

    private companion object {
        val cancellationException = CancellationException("Scope canceled by testOnUIThread because cancelScopeAfterTest was set to true.")
    }

    /**
     * Run your test block on the UI thread and inside the TestContext.
     *
     * Optionally with [cancelScopeAfterTest] you can run your test block inside a separately launched job, which
     * is canceled after your block is run. The scope waits till this is completed by joining.
     *
     * This can be handy for testing classes that rely on scopes that eventually get canceled,
     * e.g. because they are canceled automatically by a lifecycle event.
     *
     * @param block test block to be run on the UI thread and in the test context.
     *
     * @param cancelScopeAfterTest whether to cancel the coroutinescope your block ran in after it's done.
     *
     */
    fun testOnUIThread(cancelScopeAfterTest: Boolean = false, block: suspend TC.() -> Unit) {
        try {

            val createTestContext = createTestContext
            val test: suspend (CoroutineScope) -> Unit = {
                val testContext = createTestContext(it)
                yield()
                try {
                    block(testContext)
                } finally {
                    testContext.dispose()
                }
            }
            createTestContext.freeze()
            test.freeze()

            if (cancelScopeAfterTest)
                testBlockingAndCancelScope(Dispatchers.Main) { test(this) }
            else
                runBlocking(Dispatchers.Main) { test(this) }
        } catch (c: CancellationException) {
            if (!cancelScopeAfterTest || c !== cancellationException)
                throw c
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test

import kotlin.test.AfterTest
import kotlin.test.BeforeTest

actual open class BaseTest {

    @BeforeTest
    actual open fun beforeTest() {}

    @AfterTest
    actual open fun afterTest() {}
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test

import com.splendo.kaluga.logging.debug
import platform.CoreFoundation.CFRunLoopRun
import kotlin.native.concurrent.TransferMode
import kotlin.native.concurrent.Worker
import kotlin.native.concurrent.freeze
import kotlin.native.internal.test.testLauncherEntryPoint
import kotlin.system.exitProcess

fun mainBackground(args: Array<String>) {
    debug("using background thread for iOS tests")
    val worker = Worker.start(name = "kaluga-test-background")
    worker.execute(TransferMode.SAFE, { args.freeze() }) {
        val result = testLauncherEntryPoint(it)
        exitProcess(result)
    }
    CFRunLoopRun()
    error("CFRunLoopRun should never return")
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.test

import kotlin.test.AfterTest
import kotlin.test.BeforeTest

actual open class BaseTest {

    @BeforeTest
    actual open fun beforeTest() {}

    @AfterTest
    actual open fun afterTest() {}
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.test

import com.splendo.kaluga.logging.w
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.test.setMain
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.ThreadFactory
import kotlin.coroutines.CoroutineContext
import kotlin.test.AfterTest
import kotlin.test.BeforeTest

// we use this surrogate main thread because Swing/JavaFX testing on CI is unreliable
actual open class BaseTest {

    /***********************************************************
     * PLEASE KEEP IMPLEMENTATION IN SYNC WITH ANDROID VERSION *
     ***********************************************************/

    // might re-enable this if needed, but to encourage cross platform behaviour it is off
    // @Rule @JvmField
    // val instantExecutorRule = InstantTaskExecutorRule()

    private val mainDispatcher: CoroutineDispatcher by lazy {
        var mainThread: Thread? = null
        val factory = ThreadFactory { r ->
            Thread(r, "synthetic UI thread").also {
                it.uncaughtExceptionHandler =
                    Thread.UncaughtExceptionHandler { _, e -> w(throwable = e) { "error in synthetic main thread: $e" } }
                mainThread = it
            }
        }

        val executor: ExecutorService = Executors.newSingleThreadExecutor(factory)
        val d = executor.asCoroutineDispatcher()
        // Implement proper immediate support
        object : CoroutineDispatcher() {
            override fun dispatch(context: CoroutineContext, block: Runnable) {
                d.dispatch(context, block)
            }
            override fun isDispatchNeeded(context: CoroutineContext): Boolean = Thread.currentThread() != mainThread
        }
    }

    @BeforeTest
    actual open fun beforeTest() {
        Dispatchers.setMain(mainDispatcher)
    }

    @AfterTest
    actual open fun afterTest() {
        // we want to avoid triggering Swing or FX
        // Dispatchers.resetMain()
        // mainDispatcher.close()
        // mainDispatcher.cancelChildren()
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.lifecycle

import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

class LifecycleManagerObserver : LifecycleSubscriber() {

    override var manager: LifecycleSubscribable.LifecycleManager?
        get() = super.manager
        set(value) {
            super.manager = value
            _managerState.value = value
        }

    private val _managerState = MutableStateFlow<LifecycleSubscribable.LifecycleManager?>(null)
    val managerState: StateFlow<LifecycleSubscribable.LifecycleManager?> = _managerState
}

/**
 * @return The [LifecycleManagerObserver] that only reports the lifecycle of this [AppCompatActivity].
 * Will be created if need but only one instance will exist.
 *
 * For example can be used by code in the Activity itself.
 *
 * Warning: do not use this attempt to use this observer in cases where a dependent class such a Builder
 * exists over the lifespan of several Activities (e.g. when recreated due to rotation etc).
 * In particular this applies to places such as a ViewModel, which can outlive a single Activity.
 *
 */
fun AppCompatActivity.lifecycleManagerObserver(): LifecycleManagerObserver =
    getOrPutAndRemoveOnDestroyFromCache(
        onCreate = { it.subscribe(this@lifecycleManagerObserver) },
        onDestroy = { it.unsubscribe() },
        defaultValue = { LifecycleManagerObserver() }
    )

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.lifecycle

import android.app.Activity
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner

interface LifecycleSubscribable : LifecycleSubscribableMarker {

    data class LifecycleManager(
        val activity: Activity?,
        val lifecycleOwner: LifecycleOwner,
        val fragmentManager: FragmentManager
    )

    val manager: LifecycleManager?

    /**
     * Subscribes this subscribable to [LifecycleManager].
     * Called on [Lifecycle.Event.ON_CREATE]
     * @param manager The [LifecycleManager] managing the lifecycle.
     */
    fun subscribe(manager: LifecycleManager)

    /**
     * Unsubscribes this subscribable from its current subscribable.
     * Called on [Lifecycle.Event.ON_DESTROY]
     */
    fun unsubscribe()
}

open class LifecycleSubscriber : LifecycleSubscribable {

    override var manager: LifecycleSubscribable.LifecycleManager? = null

    override fun subscribe(manager: LifecycleSubscribable.LifecycleManager) {
        this.manager = manager
    }

    override fun unsubscribe() {
        this.manager = null
    }
}

/**
 * Convenience method to subscribe an [AppCompatActivity] to this [LifecycleSubscribable] using its default [LifecycleOwner] and [FragmentManager].
 */
fun LifecycleSubscribable.subscribe(activity: AppCompatActivity) = subscribe(LifecycleSubscribable.LifecycleManager(activity, activity, activity.supportFragmentManager))
fun LifecycleSubscribable.subscribe(activity: Activity?, owner: LifecycleOwner, fragmentManager: FragmentManager) = subscribe(LifecycleSubscribable.LifecycleManager(activity, owner, fragmentManager))

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.lifecycle

import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import kotlin.reflect.KClass

/**
 * @suppress
 *
 * Do not use directly, but use through [AppCompatActivity.getOrPutAndRemoveOnDestroyFromCache]
 */
val lifecycleAwareActivityCache = mutableMapOf<Pair<AppCompatActivity, KClass<*>>, Any>()

/**
 * Lifecycle aware caching on the activity level
 */
@Suppress("UNCHECKED_CAST") // our kclass parameter guards access to the map
inline fun <reified T : Any> AppCompatActivity.getOrPutAndRemoveOnDestroyFromCache(
    crossinline onCreate: (T) -> Unit = {},
    crossinline onDestroy: (T) -> Unit = {},
    defaultValue: () -> T
): T {
    val key = Pair(this, T::class)
    return lifecycleAwareActivityCache.getOrPut(key) {
        defaultValue().also {
            lifecycle.addObserver(
                object : LifecycleObserver {
                    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
                    fun onCreate() =
                        onCreate(it)

                    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
                    fun onDestroy() {
                        lifecycleAwareActivityCache.remove(key)
                        onDestroy(it)
                    }
                }
            )
        }
    } as T
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.navigation

import android.content.Intent
import android.os.Build

/**
 * Intent Flags supported for Navigation
 */
sealed class IntentFlag {

    abstract val value: Int

    object GrantReadUriPermission : IntentFlag() {
        override val value: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION
    }
    object GrantWriteUriPermission : IntentFlag() {
        override val value: Int = Intent.FLAG_GRANT_WRITE_URI_PERMISSION
    }
    object FromBackground : IntentFlag() {
        override val value: Int = Intent.FLAG_FROM_BACKGROUND
    }
    object DebugLogResolution : IntentFlag() {
        override val value: Int = Intent.FLAG_DEBUG_LOG_RESOLUTION
    }
    object ExcludeStoppedPackages : IntentFlag() {
        override val value: Int = Intent.FLAG_EXCLUDE_STOPPED_PACKAGES
    }
    object IncludeStoppedPackages : IntentFlag() {
        override val value: Int = Intent.FLAG_INCLUDE_STOPPED_PACKAGES
    }
    object GrantPersistableUriPermission : IntentFlag() {
        override val value: Int = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
    }
    object GrantPrefixUriPermission : IntentFlag() {
        override val value: Int = Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
    }
    object ActivityMatchExternal : IntentFlag() {
        override val value: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            Intent.FLAG_ACTIVITY_MATCH_EXTERNAL
        } else {
            0
        }
    }
    object ActivityNoHistory : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_NO_HISTORY
    }
    object ActivitySingleTop : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_SINGLE_TOP
    }
    object ActivityNewTask : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_NEW_TASK
    }
    object ActivityMultipleTask : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_MULTIPLE_TASK
    }
    object ActivityClearTop : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_CLEAR_TOP
    }
    object ActivityForwardResult : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_FORWARD_RESULT
    }
    object ActivityPreviousIsTop : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
    }
    object ActivityExcludeFromRecents : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
    }
    object ActivityBroughtToFront : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
    }
    object ActivityResetTaskIfNeeded : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
    }
    object ActivityLaunchedFromHistory : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
    }
    object ActivityNewDocument : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_NEW_DOCUMENT
    }
    object ActivityNoUserAction : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_NO_USER_ACTION
    }
    object ActivityReorderToFront : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
    }
    object ActivityNoAnimation : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_NO_ANIMATION
    }
    object ActivityClearTask : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_CLEAR_TASK
    }
    object ActivityTaskOnHome : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_TASK_ON_HOME
    }
    object ActivityRetainInRecents : IntentFlag() {
        override val value: Int = Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS
    }
    object ActivityLaunchAdjacent : IntentFlag() {
        override val value: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
        } else {
            0
        }
    }
    object ReceiverRegisterOnly : IntentFlag() {
        override val value: Int = Intent.FLAG_RECEIVER_REGISTERED_ONLY
    }
    object ReceiverReplacePending : IntentFlag() {
        override val value: Int = Intent.FLAG_RECEIVER_REPLACE_PENDING
    }
    object ReceiverForeground : IntentFlag() {
        override val value: Int = Intent.FLAG_RECEIVER_FOREGROUND
    }
    object ReceiverNoAbort : IntentFlag() {
        override val value: Int = Intent.FLAG_RECEIVER_NO_ABORT
    }
    object ReceiverVisibleToInstantApps : IntentFlag() {
        override val value: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
        } else {
            0
        }
    }
}

/**
 * Transforms a set of [IntentFlag] to its corresponding [Int]
 * @return The int describing the set of [IntentFlag]
 */
fun Set<IntentFlag>.toFlags(): Int = this.fold(0) { acc, flag -> acc or flag.value }

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.navigation

import android.os.Bundle
import com.splendo.kaluga.base.utils.Date

/**
 * Converts a [NavigationBundle] to a [Bundle]
 * @return The converted [Bundle]
 */
fun <R : NavigationBundleSpecRow<*>> NavigationBundle<R>.toBundle(): Bundle {
    val bundle = Bundle()

    values.entries.forEach { (key, value) ->
        mapValue(key.key ?: key.javaClass.simpleName, value, bundle)
    }

    return bundle
}

internal fun mapValue(key: String, value: NavigationBundleValue<*>, bundle: Bundle) {
    when (value) {
        is NavigationBundleValue.UnitValue -> {}
        is NavigationBundleValue.BooleanValue -> bundle.putBoolean(key, value.value)
        is NavigationBundleValue.BooleanArrayValue -> bundle.putBooleanArray(key, value.value)
        is NavigationBundleValue.ByteValue -> bundle.putByte(key, value.value)
        is NavigationBundleValue.ByteArrayValue -> bundle.putByteArray(key, value.value)
        is NavigationBundleValue.BundleValue<*> -> bundle.putBundle(key, value.value.toBundle())
        is NavigationBundleValue.CharValue -> bundle.putChar(key, value.value)
        is NavigationBundleValue.CharArrayValue -> bundle.putCharArray(key, value.value)
        is NavigationBundleValue.CharSequenceValue -> bundle.putCharSequence(key, value.value)
        is NavigationBundleValue.DoubleValue -> bundle.putDouble(key, value.value)
        is NavigationBundleValue.DoubleArrayValue -> bundle.putDoubleArray(key, value.value)
        is NavigationBundleValue.FloatValue -> bundle.putFloat(key, value.value)
        is NavigationBundleValue.FloatArrayValue -> bundle.putFloatArray(key, value.value)
        is NavigationBundleValue.IntegerValue -> bundle.putInt(key, value.value)
        is NavigationBundleValue.IntegerArrayValue -> bundle.putIntArray(key, value.value)
        is NavigationBundleValue.LongValue -> bundle.putLong(key, value.value)
        is NavigationBundleValue.LongArrayValue -> bundle.putLongArray(key, value.value)
        is NavigationBundleValue.SerializedValue<*> -> bundle.putString(key, value.valueString)
        is NavigationBundleValue.ShortValue -> bundle.putShort(key, value.value)
        is NavigationBundleValue.ShortArrayValue -> bundle.putShortArray(key, value.value)
        is NavigationBundleValue.StringValue -> bundle.putString(key, value.value)
        is NavigationBundleValue.StringArrayValue -> bundle.putStringArrayList(key, ArrayList(value.value))
        is NavigationBundleValue.OptionalValue<*> -> value.optionalValue?.let { mapValue(key, it, bundle) }
        is NavigationBundleValue.DateValue -> bundle.putLong(key, value.value.millisecondSinceEpoch)
        is NavigationBundleValue.DateArrayValue ->
            bundle.putLongArray(key, LongArray(value.value.size) { value.value[it].millisecondSinceEpoch })
    }
}

/**
 * Converts a [Bundle] to a [NavigationBundle] matching a given [NavigationBundleSpec]
 * @param spec The [NavigationBundleSpec] used to create the [NavigationBundle]
 * @throws [BundleConversionError] if the [Bundle] does not contain the correct keys or values associated with the [NavigationBundleSpec]
 */
fun <R : NavigationBundleSpecRow<*>> Bundle.toNavigationBundle(spec: NavigationBundleSpec<R>): NavigationBundle<R> {
    return NavigationBundle(
        spec,
        spec.rows.associate { row ->
            mapValue(row.key ?: row.javaClass.simpleName, row.associatedType)?.let {
                Pair(row, it)
            } ?: throw BundleConversionError()
        }
    )
}

/**
 * Converts a [Bundle] to a property associated with a [NavigationBundleSpecType]
 * Requires that the [Bundle] is described by a [SingleValueNavigationSpec] matching the [NavigationBundleSpecType]
 * @return The type stored in the bundle
 * @throws [BundleConversionError] if the [Bundle] is not associated with a [SingleValueNavigationSpec]
 */
fun <R> Bundle.toTypedProperty(type: NavigationBundleSpecType<R>): R {
    return toNavigationBundle(SingleValueNavigationSpec(type)).get(type)
}

/**
 * Error indicating a [Bundle] could not be converted to a [NavigationBundle]
 */
class BundleConversionError : Exception()

internal fun Bundle.mapValue(key: String, specType: NavigationBundleSpecType<*>): NavigationBundleValue<*>? {
    return when (specType) {
        is NavigationBundleSpecType.UnitType -> specType.convertValue(Unit)
        is NavigationBundleSpecType.BooleanType -> specType.convertValue(getBoolean(key))
        is NavigationBundleSpecType.BooleanArrayType -> getBooleanArray(key)?.let { specType.convertValue(it) }
        is NavigationBundleSpecType.ByteType -> specType.convertValue(getByte(key))
        is NavigationBundleSpecType.ByteArrayType -> getByteArray(key)?.let { specType.convertValue(it) }
        is NavigationBundleSpecType.BundleType<*> -> {
            getBundle(key)?.let {
                NavigationBundleValue.BundleValue(it.toNavigationBundle(specType.spec))
            }
        }
        is NavigationBundleSpecType.CharType -> specType.convertValue(getChar(key))
        is NavigationBundleSpecType.CharArrayType -> getCharArray(key)?.let { specType.convertValue(it) }
        is NavigationBundleSpecType.CharSequenceType -> getCharSequence(key)?.let { specType.convertValue(it) }
        is NavigationBundleSpecType.DoubleType -> specType.convertValue(getDouble(key))
        is NavigationBundleSpecType.DoubleArrayType -> getDoubleArray(key)?.let { specType.convertValue(it) }
        is NavigationBundleSpecType.FloatType -> specType.convertValue(getFloat(key))
        is NavigationBundleSpecType.FloatArrayType -> getFloatArray(key)?.let { specType.convertValue(it) }
        is NavigationBundleSpecType.IntegerType -> specType.convertValue(getInt(key))
        is NavigationBundleSpecType.IntegerArrayType -> getIntArray(key)?.let { specType.convertValue(it) }
        is NavigationBundleSpecType.LongType -> specType.convertValue(getLong(key))
        is NavigationBundleSpecType.LongArrayType -> getLongArray(key)?.let { specType.convertValue(it) }
        is NavigationBundleSpecType.SerializedType<*> -> getString(key)?.let { specType.generateValue(it) }
        is NavigationBundleSpecType.ShortType -> specType.convertValue(getShort(key))
        is NavigationBundleSpecType.ShortArrayType -> getShortArray(key)?.let { specType.convertValue(it) }
        is NavigationBundleSpecType.StringType -> getString(key)?.let { specType.convertValue(it) }
        is NavigationBundleSpecType.StringArrayType -> getStringArrayList(key)?.let { specType.convertValue(it) }
        is NavigationBundleSpecType.OptionalType<*> -> {
            val optionalType = specType.type
            if (containsKey(key)) {
                mapValue(key, optionalType)
            } else {
                specType.convertValue(null)
            }
        }
        is NavigationBundleSpecType.DateType -> getLong(key).let { value ->
            specType.convertValue(Date.epoch(value))
        }
        is NavigationBundleSpecType.DateArrayType -> getLongArray(key)?.let { array ->
            specType.convertValue(array.map { Date.epoch(it) })
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.navigation

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.annotation.AnimRes
import androidx.annotation.AnimatorRes
import androidx.annotation.IdRes
import androidx.fragment.app.DialogFragment
import java.net.URL

/**
 * Spec used by [Navigator] to determine how to perform Navigation
 */
sealed class NavigationSpec {

    /**
     * Navigates to a new [android.app.Activity]
     * The [Navigator] automatically adds the [NavigationBundle] of the [NavigationAction] as a [Bundle] to its [Intent]
     * @param activityClass [Class] of the Activity to navigate to
     * @param flags Set of [IntentFlag] to add to the navigation [Intent]
     * @param requestCode Optional request code to add. If passed, [android.app.Activity.startActivityForResult] is called. Otherwise uses [android.app.Activity.startActivity]
     */
    data class Activity<A : android.app.Activity>(val activityClass: Class<A>, val flags: Set<IntentFlag> = emptySet(), val requestCode: Int? = null) : NavigationSpec()

    /**
     * Closes the current [android.app.Activity]
     * @param result Optional result code
     */
    data class Close(val result: Int? = null) : NavigationSpec()

    /**
     * Shows a [androidx.fragment.app.Fragment]
     * @param containerId The identifier of the View containing the fragment
     * @param type The [Type] of transaction. Defaults to [Type.Replace]
     * @param tag Optional tag of the fragment transaction
     * @param backStackSettings The [BackStackSettings] of the transaction. Defaults to [BackStackSettings.DontAdd]
     * @param animationSettings Optional [AnimationSettings] for the transaction
     * @param createFragment Function to create the [androidx.fragment.app.Fragment]
     */
    data class Fragment(
        @IdRes val containerId: Int,
        val type: Type = Type.Replace,
        val tag: String? = null,
        val backStackSettings: BackStackSettings = BackStackSettings.DontAdd,
        val animationSettings: AnimationSettings? = null,
        val createFragment: () -> androidx.fragment.app.Fragment
    ) : NavigationSpec() {

        /**
         * Type of fragment transaction.
         */
        sealed class Type {
            /**
             * Adds a [androidx.fragment.app.Fragment] without removing any other fragments that may have been added
             */
            object Add : Type()

            /**
             * Replaces the existing [androidx.fragment.app.Fragment]. If no fragment has been added yet, this just adds the fragment.
             */
            object Replace : Type()
        }

        /**
         * Settings for whether the [androidx.fragment.app.Fragment] should be added to the Backstack
         */
        sealed class BackStackSettings {
            /**
             * Doesn't add the [androidx.fragment.app.Fragment] to the backstack
             */
            object DontAdd : BackStackSettings()

            /**
             * Adds the [androidx.fragment.app.Fragment] to the backstack
             * @param name The name with which to add the fragment
             */
            data class Add(val name: String? = null) : BackStackSettings()
        }

        /**
         * Settings for animation during the fragment transaction
         * @param enter Enter animation
         * @param exit Exit animation
         * @param popEnter Pop Enter Animation
         * @param popExit Pop Exit Animation
         */
        data class AnimationSettings(
            @AnimatorRes @AnimRes val enter: Int = 0,
            @AnimatorRes @AnimRes val exit: Int = 0,
            @AnimatorRes @AnimRes val popEnter: Int = 0,
            @AnimatorRes @AnimRes val popExit: Int = 0
        )
    }

    /**
     * Removes a [Fragment] with a given tag
     * @param tag The tag of the [Fragment] to remove
     */
    data class RemoveFragment(val tag: String) : NavigationSpec()

    /**
     * Shows a [DialogFragment]
     * @param tag Optional tag to add to the Dialog
     * @param createDialog Function to create the [DialogFragment] to display
     */
    data class Dialog(val tag: String? = null, val createDialog: () -> DialogFragment) : NavigationSpec()

    /**
     * Dismisses a [DialogFragment] with a given Tag
     * @param tag The tag of the [DialogFragment] to remove
     */
    data class DismissDialog(val tag: String) : NavigationSpec()

    /**
     * Shows the Camera
     * @param type The [Type] of media to capture
     * @param requestCode The request code added to the intent
     * @param uri Optiona [Uri] indicating where the result should be stored
     */
    data class Camera(val type: Type, val requestCode: Int, val uri: Uri?) : NavigationSpec() {

        /**
         * Type of media the camera can capture
         */
        sealed class Type {
            object Image : Type()
            object Video : Type()
        }
    }

    /**
     * Shows the Email app
     * @param emailSettings Settings for the email
     */
    data class Email(val emailSettings: EmailSettings) : NavigationSpec() {

        /**
         * The type of formatting used for composing the email
         */
        sealed class Type {
            object Plain : Type()
            object Stylized : Type()
        }

        /**
         * Settings for the email
         * @param type The [Type] of formatting to be used
         * @param to The list of recipent emails
         * @param cc The list of cc emails
         * @param bcc The list of bcc emails
         * @param subject Optional subject of the email
         * @param body Optional body of the email
         * @param attachments List of [Uri] pointing to attachments to add
         */
        data class EmailSettings(
            val type: Type = Type.Plain,
            val to: List<String> = emptyList(),
            val cc: List<String> = emptyList(),
            val bcc: List<String> = emptyList(),
            val subject: String? = null,
            val body: String? = null,
            val attachments: List<Uri> = emptyList()
        )
    }

    /**
     * Opens a File Selector
     * @param requestCode The request code added to the intent
     * @param fileSelectorSettings The [FileSelectorSettings] used to configure the file selection
     */
    data class FileSelector(val requestCode: Int, val fileSelectorSettings: FileSelectorSettings) : NavigationSpec() {

        /**
         * Settings to configure the file selection criteria
         * @param type List of file types to select
         * @param allowMultiple Used to indicate the multiple files can be selected
         * @param localOnly Used to indicate that only data that is on the local device should be returned
         */
        data class FileSelectorSettings(val type: String, val allowMultiple: Boolean = false, val localOnly: Boolean = true)
    }

    /**
     * Opens up the Phone screen
     * @param type The [Type] of phone screen to show
     * @param phoneNumber The phone number to dial
     */
    data class Phone(val type: Type, val phoneNumber: String) : NavigationSpec() {
        /**
         * Type of Phone Screen to open
         */
        sealed class Type {
            /**
             * Opens the Dialer screen and pre-fills the phone number
             */
            object Dial : Type()

            /**
             * Opens the phone screen and calls the phone number
             */
            object Call : Type()
        }
    }

    /**
     * Opens the Settings screen
     * @param type [Type] of settings screen to open
     */
    data class Settings(val type: Type) : NavigationSpec() {
        sealed class Type {
            object General : Type()
            object Wireless : Type()
            object AirplaneMode : Type()
            object Wifi : Type()
            object Apn : Type()
            object Bluetooth : Type()
            object Date : Type()
            object Locale : Type()
            object InputMethod : Type()
            object Display : Type()
            object Security : Type()
            object LocationSource : Type()
            object InternalStorage : Type()
            object MemoryCard : Type()
            object AppDetails : Type()
        }
    }

    /**
     * Opens the Text messaeger screen
     * @param settings The [TextMessengerSettings] to configure the message
     */
    data class TextMessenger(val settings: TextMessengerSettings) : NavigationSpec() {
        /**
         * The type of Text Message to send
         */
        sealed class Type {
            object Plain : Type()
            object Image : Type()
            object Video : Type()
        }

        /**
         * Settings for the text message
         * @param type [Type] of text message to send
         * @param recipients List of recipients
         * @param subject Optional subject of the message
         * @param body Optional body of the message
         * @param attachments List of [Uri] pointing to attachments to add
         */
        data class TextMessengerSettings(val type: Type = Type.Plain, val recipients: List<String>, val subject: String? = null, val body: String? = null, val attachments: List<Uri> = emptyList())
    }

    /**
     * Opens the browser
     * @param url The [URL] to open in the browser
     */
    data class Browser(val url: URL, val viewType: Type) : NavigationSpec() {
        sealed class Type {
            object CustomTab : Type()
            object Normal : Type()
        }
    }

    /**
     * Opens a Third Party app if installed on the phone or navigates to the store if it does not exist.
     * @param packageName The name of the package for which to open the store
     * @param openMode The [OpenMode] used to determine how to handle whether to open the app or the PlayStore
     */
    data class ThirdPartyApp(val packageName: String, val openMode: OpenMode = OpenMode.FALLBACK_TO_STORE) : NavigationSpec() {
        enum class OpenMode {
            /**
             * Immediately opens the store
             */
            FORCE_STORE,

            /**
             * Only navigates when the app is installed
             */
            ONLY_WHEN_INSTALLED,

            /**
             * Opens the PlayStore if the app is not installed
             */
            FALLBACK_TO_STORE
        }
    }

    /**
     * Navigates according to a given [Intent]
     * @param intent The [Intent] to navigate to
     */
    data class CustomIntent(val intent: Intent) : NavigationSpec()
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.navigation

import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.provider.MediaStore
import android.provider.Settings
import androidx.browser.customtabs.CustomTabsIntent
import androidx.fragment.app.DialogFragment
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscriber

actual interface Navigator<A : NavigationAction<*>> : LifecycleSubscribableMarker {
    actual fun navigate(action: A)
}

/**
 * Implementation of [Navigator]. Takes a mapper function to map all [NavigationAction] to a [NavigationSpec]
 * Whenever [navigate] is called, this class maps it to a [NavigationSpec] and performs navigation according to that
 * Requires to be subscribed to an activity via [subscribe] to work
 * @param navigationMapper A function mapping the [NavigationAction] to [NavigationSpec]
 */
class ActivityNavigator<A : NavigationAction<*>>(private val navigationMapper: (A) -> NavigationSpec) : Navigator<A>, LifecycleSubscribable by LifecycleSubscriber() {

    override fun navigate(action: A) {
        navigate(navigationMapper.invoke(action), action.bundle)
    }

    private fun navigate(spec: NavigationSpec, bundle: NavigationBundle<*>?) {
        when (spec) {
            is NavigationSpec.Activity<*> -> navigateToActivity(spec, bundle)
            is NavigationSpec.Close -> closeActivity(spec, bundle)
            is NavigationSpec.Fragment -> navigateToFragment(spec)
            is NavigationSpec.RemoveFragment -> removeFragment(spec)
            is NavigationSpec.Dialog -> navigateToDialog(spec)
            is NavigationSpec.DismissDialog -> dismissDialog(spec)
            is NavigationSpec.Camera -> navigateToCamera(spec)
            is NavigationSpec.Email -> navigateToEmail(spec)
            is NavigationSpec.FileSelector -> navigateToFileSelector(spec)
            is NavigationSpec.Phone -> navigateToPhone(spec)
            is NavigationSpec.Settings -> navigateToSettings(spec)
            is NavigationSpec.TextMessenger -> navigateToMessenger(spec)
            is NavigationSpec.Browser -> navigateToBrowser(spec)
            is NavigationSpec.ThirdPartyApp -> navigateToThirdPartyApp(spec)
            is NavigationSpec.CustomIntent -> navigateToIntent(spec)
        }
    }

    private fun navigateToActivity(
        activitySpec: NavigationSpec.Activity<*>,
        bundle: NavigationBundle<*>?
    ) {
        assert(manager?.activity != null)
        val activity = manager?.activity ?: return
        val intent = Intent(activity, activitySpec.activityClass).apply {
            bundle?.let {
                putExtras(it.toBundle())
            }
            flags = activitySpec.flags.toFlags()
        }
        activitySpec.requestCode?.let {
            activity.startActivityForResult(intent, it)
        } ?: activity.startActivity(intent)
    }

    private fun closeActivity(closeSpec: NavigationSpec.Close, bundle: NavigationBundle<*>?) {
        assert(manager?.activity != null)
        val activity = manager?.activity ?: return
        closeSpec.result?.let { resultCode ->
            val data = Intent().apply {
                bundle?.let {
                    putExtras(it.toBundle())
                }
            }
            activity.setResult(resultCode, data)
        }
        activity.finish()
    }

    private fun navigateToFragment(fragmentSpec: NavigationSpec.Fragment) {
        assert(manager?.fragmentManager != null)
        val fragmentManager = manager?.fragmentManager ?: return
        val transaction = fragmentManager.beginTransaction()

        when (val backtrackSettings = fragmentSpec.backStackSettings) {
            is NavigationSpec.Fragment.BackStackSettings.Add -> transaction.addToBackStack(
                backtrackSettings.name
            )
        }

        fragmentSpec.animationSettings?.let { animationSettings ->
            transaction.setCustomAnimations(
                animationSettings.enter,
                animationSettings.exit,
                animationSettings.popEnter,
                animationSettings.popExit
            )
        }

        val fragment = fragmentSpec.createFragment()
        when (fragmentSpec.type) {
            is NavigationSpec.Fragment.Type.Add -> transaction.add(
                fragmentSpec.containerId,
                fragment,
                fragmentSpec.tag
            )
            is NavigationSpec.Fragment.Type.Replace -> transaction.replace(
                fragmentSpec.containerId,
                fragment,
                fragmentSpec.tag
            )
        }

        transaction.commit()
    }

    private fun removeFragment(removeFragmentSpec: NavigationSpec.RemoveFragment) {
        assert(manager?.fragmentManager != null)
        val fragmentManager = manager?.fragmentManager ?: return
        val fragment = fragmentManager.findFragmentByTag(removeFragmentSpec.tag) ?: return

        val transaction = fragmentManager.beginTransaction()
        transaction.remove(fragment)
        transaction.commit()
    }

    private fun navigateToDialog(dialogSpec: NavigationSpec.Dialog) {
        assert(manager?.fragmentManager != null)
        val fragmentManager = manager?.fragmentManager ?: return
        dialogSpec.createDialog().show(fragmentManager, dialogSpec.tag)
    }

    private fun dismissDialog(spec: NavigationSpec.DismissDialog) {
        assert(manager?.fragmentManager != null)
        val fragmentManager = manager?.fragmentManager ?: return
        val dialog = fragmentManager.findFragmentByTag(spec.tag) as? DialogFragment ?: return
        dialog.dismiss()
    }

    private fun navigateToCamera(cameraSpec: NavigationSpec.Camera) {
        assert(manager?.activity != null)
        val activity = manager?.activity ?: return
        val intent = when (cameraSpec.type) {
            is NavigationSpec.Camera.Type.Image -> Intent(MediaStore.ACTION_IMAGE_CAPTURE)
            is NavigationSpec.Camera.Type.Video -> Intent(MediaStore.ACTION_VIDEO_CAPTURE)
        }

        cameraSpec.uri?.let { uri ->
            intent.putExtra(MediaStore.EXTRA_OUTPUT, uri)
        }

        intent.resolveActivity(activity.packageManager)?.let {
            activity.startActivityForResult(intent, cameraSpec.requestCode)
        }
    }

    private fun navigateToEmail(emailSpec: NavigationSpec.Email) {
        assert(manager?.activity != null)
        val activity = manager?.activity ?: return
        val settings = emailSpec.emailSettings
        val intent = when (settings.attachments.size) {
            0 -> Intent(Intent.ACTION_SEND)
            1 -> Intent(Intent.ACTION_SEND).apply {
                putExtra(
                    Intent.EXTRA_STREAM,
                    settings.attachments[0]
                )
            }
            else -> Intent(Intent.ACTION_SEND_MULTIPLE).apply {
                putExtra(
                    Intent.EXTRA_STREAM,
                    ArrayList(
                        settings.attachments
                    )
                )
            }
        }.apply {
            data = Uri.parse("mailto:")
            type = when (settings.type) {
                is NavigationSpec.Email.Type.Plain -> "text/plain"
                is NavigationSpec.Email.Type.Stylized -> "*/*"
            }
            if (settings.to.isNotEmpty()) {
                putExtra(Intent.EXTRA_EMAIL, settings.to.toTypedArray())
            }
            if (settings.cc.isNotEmpty()) {
                putExtra(Intent.EXTRA_CC, settings.cc.toTypedArray())
            }
            if (settings.bcc.isNotEmpty()) {
                putExtra(Intent.EXTRA_BCC, settings.bcc.toTypedArray())
            }
            settings.subject?.let { putExtra(Intent.EXTRA_SUBJECT, it) }
            settings.body?.let { putExtra(Intent.EXTRA_TEXT, it) }
        }

        intent.resolveActivity(activity.packageManager)?.let {
            activity.startActivity(intent)
        }
    }

    private fun navigateToFileSelector(fileSelectorSpec: NavigationSpec.FileSelector) {
        assert(manager?.activity != null)
        val activity = manager?.activity ?: return
        val settings = fileSelectorSpec.fileSelectorSettings
        val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
            type = settings.type
            putExtra(Intent.EXTRA_ALLOW_MULTIPLE, settings.allowMultiple)
            putExtra(Intent.EXTRA_LOCAL_ONLY, settings.localOnly)
        }

        intent.resolveActivity(activity.packageManager)?.let {
            activity.startActivityForResult(intent, fileSelectorSpec.requestCode)
        }
    }

    private fun navigateToPhone(phoneSpec: NavigationSpec.Phone) {
        assert(manager?.activity != null)
        val activity = manager?.activity ?: return

        val intent = when (phoneSpec.type) {
            is NavigationSpec.Phone.Type.Dial -> Intent(Intent.ACTION_DIAL)
            is NavigationSpec.Phone.Type.Call -> Intent(Intent.ACTION_CALL)
        }.apply {
            data = Uri.parse("tel:${phoneSpec.phoneNumber}")
        }

        intent.resolveActivity(activity.packageManager)?.let {
            activity.startActivity(intent)
        }
    }

    private fun navigateToSettings(settingsSpec: NavigationSpec.Settings) {
        assert(manager?.activity != null)
        val activity = manager?.activity ?: return
        val intent = when (settingsSpec.type) {
            is NavigationSpec.Settings.Type.General -> Intent(Settings.ACTION_SETTINGS)
            is NavigationSpec.Settings.Type.Wireless -> Intent(Settings.ACTION_WIRELESS_SETTINGS)
            is NavigationSpec.Settings.Type.AirplaneMode -> Intent(Settings.ACTION_AIRPLANE_MODE_SETTINGS)
            is NavigationSpec.Settings.Type.Wifi -> Intent(Settings.ACTION_WIFI_SETTINGS)
            is NavigationSpec.Settings.Type.Apn -> Intent(Settings.ACTION_APN_SETTINGS)
            is NavigationSpec.Settings.Type.Bluetooth -> Intent(Settings.ACTION_BLUETOOTH_SETTINGS)
            is NavigationSpec.Settings.Type.Date -> Intent(Settings.ACTION_DATE_SETTINGS)
            is NavigationSpec.Settings.Type.Locale -> Intent(Settings.ACTION_LOCALE_SETTINGS)
            is NavigationSpec.Settings.Type.InputMethod -> Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)
            is NavigationSpec.Settings.Type.Display -> Intent(Settings.ACTION_DISPLAY_SETTINGS)
            is NavigationSpec.Settings.Type.Security -> Intent(Settings.ACTION_SECURITY_SETTINGS)
            is NavigationSpec.Settings.Type.LocationSource -> Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
            is NavigationSpec.Settings.Type.InternalStorage -> Intent(Settings.ACTION_INTERNAL_STORAGE_SETTINGS)
            is NavigationSpec.Settings.Type.MemoryCard -> Intent(Settings.ACTION_MEMORY_CARD_SETTINGS)
            is NavigationSpec.Settings.Type.AppDetails -> {
                val uri = Uri.fromParts("package", activity.packageName, null)
                Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri)
            }
        }

        intent.resolveActivity(activity.packageManager)?.let {
            activity.startActivity(intent)
        }
    }

    private fun navigateToMessenger(messengerSpec: NavigationSpec.TextMessenger) {
        assert(manager?.activity != null)
        val activity = manager?.activity ?: return
        val settings = messengerSpec.settings
        val intent = when (settings.attachments.size) {
            0 -> Intent(Intent.ACTION_SEND)
            1 -> Intent(Intent.ACTION_SEND).apply {
                putExtra(
                    Intent.EXTRA_STREAM,
                    settings.attachments[0]
                )
            }
            else -> Intent(Intent.ACTION_SEND_MULTIPLE).apply {
                putExtra(
                    Intent.EXTRA_STREAM,
                    ArrayList(
                        settings.attachments
                    )
                )
            }
        }.apply {
            val recipients = settings.recipients.fold("") { acc, recipient -> if (acc.isNotEmpty()) "$acc;$recipient" else recipient }
            data = Uri.parse("smsto:$recipients")
            type = when (settings.type) {
                is NavigationSpec.TextMessenger.Type.Plain -> "text/plain"
                is NavigationSpec.TextMessenger.Type.Image -> "image/*"
                is NavigationSpec.TextMessenger.Type.Video -> "video/*"
            }
            settings.subject?.let { putExtra("subject", it) }
            settings.body?.let { putExtra("sms_body", it) }
        }

        intent.resolveActivity(activity.packageManager)?.let {
            activity.startActivity(intent)
        }
    }

    private fun navigateToBrowser(browserSpec: NavigationSpec.Browser) {
        assert(manager?.activity != null)
        val activity = manager?.activity ?: return

        when (browserSpec.viewType) {
            NavigationSpec.Browser.Type.CustomTab -> {
                val builder = CustomTabsIntent.Builder()
                val customTabsIntent = builder.build()
                customTabsIntent.intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                customTabsIntent.launchUrl(
                    activity,
                    Uri.parse(browserSpec.url.toURI().toString())
                )
            }
            NavigationSpec.Browser.Type.Normal -> {

                val uri = Uri.parse(browserSpec.url.toURI().toString())
                val intent = Intent(Intent.ACTION_VIEW, uri)

                intent.resolveActivity(activity.packageManager)?.let {
                    activity.startActivity(intent)
                }
            }
        }
    }

    private fun navigateToThirdPartyApp(thirdPartyAppSpec: NavigationSpec.ThirdPartyApp) {
        when (thirdPartyAppSpec.openMode) {
            NavigationSpec.ThirdPartyApp.OpenMode.ONLY_WHEN_INSTALLED -> navigateToThirdPartyApp(
                thirdPartyAppSpec.packageName
            )
            NavigationSpec.ThirdPartyApp.OpenMode.FORCE_STORE -> navigateToPlayStore(
                thirdPartyAppSpec.packageName
            )
            NavigationSpec.ThirdPartyApp.OpenMode.FALLBACK_TO_STORE -> {
                if (!navigateToThirdPartyApp(thirdPartyAppSpec.packageName)) {
                    navigateToPlayStore(thirdPartyAppSpec.packageName)
                }
            }
        }
    }

    private fun navigateToThirdPartyApp(packageName: String): Boolean {
        assert(manager?.activity != null)
        val activity = manager?.activity ?: return false

        val intent = activity.packageManager.getLaunchIntentForPackage(packageName) ?: return false
        return try {
            activity.startActivity(intent)
            true
        } catch (e: ActivityNotFoundException) {
            false
        }
    }

    private fun navigateToPlayStore(packageName: String) {
        assert(manager?.activity != null)
        val activity = manager?.activity ?: return

        try {
            activity.startActivity(
                Intent(
                    Intent.ACTION_VIEW,
                    Uri.parse("market://details?id=$packageName")
                )
            )
        } catch (e: ActivityNotFoundException) {
            activity.startActivity(
                Intent(
                    Intent.ACTION_VIEW,
                    Uri.parse("https://play.google.com/store/apps/details?id=$packageName")
                )
            )
        }
    }

    private fun navigateToIntent(intentSpec: NavigationSpec.CustomIntent) {
        assert(manager?.activity != null)
        val activity = manager?.activity ?: return

        activity.startActivity(intentSpec.intent)
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.

 */

@file:JvmName("DisposableJVMKt")
package com.splendo.kaluga.architecture.observable

actual class SimpleDisposable actual constructor(onDispose: DisposeHandler) : BaseSimpleDisposable(onDispose)

internal actual fun <R : T, T, OO : ObservableOptional<R>> addObserver(observation: Observation<R, T, OO>, observer: (R) -> Unit) {
    observation.observers.add(observer)
}

internal actual fun <R : T, T, OO : ObservableOptional<R>> removeObserver(observation: Observation<R, T, OO>, observer: (R) -> Unit) {
    observation.observers.remove(observer)
}

internal actual fun <R : T, T, OO : ObservableOptional<R>> observers(observation: Observation<R, T, OO>): List<(R) -> Unit> =
    observation.observers

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.

 */
@file:JvmName("AndroidObservable")

package com.splendo.kaluga.architecture.observable

import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.asLiveData
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext
import kotlin.properties.ReadOnlyProperty

actual interface WithState<T> {
    actual val stateFlow: StateFlow<T>
    actual val valueDelegate: ReadOnlyProperty<Any?, T>
    val liveData: LiveData<T>
        get() = stateFlow.asLiveData()
}

val <R : T, T, OO : ObservableOptional<R>> BasicSubject<R, T, OO>.liveDataObserver: Observer<T>
    get() = Observer<T> { this.post(it) }

private fun <B, R : T, T, OO : ObservableOptional<R>> B.mutableLiveData(): MutableLiveData<R> where B : BasicSubject<R, T, OO>, B : WithMutableState<R> {
    val mediatorLiveData = MediatorLiveData<R>()
    mediatorLiveData.addSource(stateFlow.asLiveData()) { value ->
        mediatorLiveData.postValue(value)
    }

    return mediatorLiveData
}

fun <T> LiveData<T>.observeOnCoroutine(
    coroutineScope: CoroutineScope,
    coroutineContext: CoroutineContext = Dispatchers.Main.immediate,
    observer: Observer<T>
) {
    coroutineScope.launch(coroutineContext) {
        observeForever(observer)
        awaitCancellation()
    }.invokeOnCompletion {
        removeObserver(observer)
    }
}

actual abstract class BaseSubject<R : T, T, OO : ObservableOptional<R>> actual constructor(
    observation: Observation<R, T, OO>,
    stateFlowToBind: () -> StateFlow<R?>
) : AbstractBaseSubject<R, T, OO>(observation, stateFlowToBind) {

    private var coroutineScope: CoroutineScope? = null
    private val mutableLiveDataDelegate = lazy {
        createLiveData().also {
            coroutineScope?.let { coroutineScope ->
                it.observeOnCoroutine(coroutineScope, observer = liveDataObserver())
            }
        }
    }
    val mutableLiveData by mutableLiveDataDelegate
    protected abstract fun createLiveData(): MutableLiveData<R>
    abstract fun liveDataObserver(): Observer<R>

    final override fun bind(coroutineScope: CoroutineScope, context: CoroutineContext) {
        super.bind(coroutineScope, context)
        this.coroutineScope = coroutineScope
        if (mutableLiveDataDelegate.isInitialized()) {
            mutableLiveData.observeOnCoroutine(coroutineScope, observer = liveDataObserver())
        }
    }
}

actual abstract class BaseUninitializedSubject<T> actual constructor(
    observation: ObservationUninitialized<T>
) : AbstractBaseUninitializedSubject<T>(observation) {

    override fun createLiveData(): MutableLiveData<T> {
        val mediatorLiveData = MediatorLiveData<T>()
        mediatorLiveData.addSource(stateFlow.asLiveData()) { value ->
            mediatorLiveData.postValue(value)
        }
        return mediatorLiveData
    }

    override fun liveDataObserver() = Observer<T> { value -> value?.let { post(it) } }
}

actual abstract class BaseInitializedSubject<T> actual constructor(observation: ObservationInitialized<T>) :
    AbstractBaseInitializedSubject<T>(observation) {

    override fun createLiveData(): MutableLiveData<T> = this.mutableLiveData()
    override fun liveDataObserver() = liveDataObserver

    actual constructor(
        initialValue: ObservableOptional.Value<T>
    ) : this (ObservationInitialized(initialValue))
}

actual abstract class BaseDefaultSubject<R : T?, T> actual constructor(
    observation: ObservationDefault<R, T?>
) : AbstractBaseDefaultSubject<R, T>(observation) {

    override fun createLiveData(): MutableLiveData<R> = this.mutableLiveData()
    override fun liveDataObserver() = Observer<R> { post(it) }

    actual constructor(
        defaultValue: ObservableOptional.Value<R>,
        initialValue: ObservableOptional.Value<T?>
    ) : this(observation = ObservationDefault<R, T?>(defaultValue, initialValue))
}

fun <T> WithState<T>.observeOnLifecycle(
    lifecycleOwner: LifecycleOwner,
    filter: suspend (T) -> Boolean = { true },
    onNext: (T) -> Unit
) =
    observeOnLifecycle(lifecycleOwner, filter = filter, transform = { it }, onNext = onNext)

fun <T> WithState<T?>.observeNotNullOnLifecycle(
    lifecycleOwner: LifecycleOwner,
    filter: suspend (T) -> Boolean = { true },
    onNext: (T) -> Unit
) =
    observeOnLifecycle(
        lifecycleOwner,
        filter = { value -> value?.let { filter(it) } ?: false },
        transform = { it!! },
        onNext = onNext
    )

fun <T, R> WithState<T>.observeOnLifecycle(
    lifecycleOwner: LifecycleOwner,
    filter: suspend (T) -> Boolean = { true },
    transform: suspend (T) -> R,
    onNext: (R) -> Unit
) = lifecycleOwner.lifecycleScope.launch {
    stateFlow.filter { filter(it) }.map { transform(it) }.collect {
        onNext(it)
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.viewmodel

import android.os.Bundle
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity

/**
 * Convenience [AppCompatActivity] that is bound to a [ViewModel]
 */
abstract class KalugaViewModelActivity<VM : BaseViewModel>(@LayoutRes layout: Int = 0) : AppCompatActivity(layout) {

    abstract val viewModel: VM

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.bind(this)
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.viewmodel

import android.os.Bundle
import android.view.View
import androidx.fragment.app.DialogFragment

/**
 * Convenience [DialogFragment] that is bound to a [ViewModel]
 */
abstract class KalugaViewModelDialogFragment<VM : BaseViewModel> : DialogFragment() {

    abstract val viewModel: VM

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.bind(this)
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.viewmodel

import android.os.Bundle
import android.view.View
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment

/**
 * Convenience [Fragment] that is bound to a [ViewModel]
 */
abstract class KalugaViewModelFragment<VM : BaseViewModel>(@LayoutRes layout: Int = 0) : Fragment(layout) {

    abstract val viewModel: VM

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.bind(this)
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.viewmodel

import android.app.Activity
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribableMarker
import com.splendo.kaluga.architecture.lifecycle.subscribe
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KProperty1
import kotlin.reflect.KVisibility
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.starProjectedType

/**
 * [LifecycleObserver] used to manage the lifecycle of a [BaseViewModel]
 * @param viewModel The [BaseViewModel] whose lifecycle is managed
 * @param activity The [Activity] managing the lifecycle
 * @param fragmentManager The [FragmentManager] for this lifecycle
 */
class KalugaViewModelLifecycleObserver<VM : BaseViewModel> internal constructor(
    private val viewModel: VM,
    private val activity: Activity?,
    private val lifecycleOwner: LifecycleOwner,
    private val fragmentManager: FragmentManager
) : LifecycleObserver {

    private val lifecycleSubscribables: List<LifecycleSubscribable> by lazy {
        viewModel::class.memberProperties
            .filter { it !is KMutableProperty1 }
            .mapNotNull { it as? KProperty1<VM, Any?> }
            .filter {
                it.getter.visibility == KVisibility.PUBLIC &&
                    it.getter.returnType.isSubtypeOf(LifecycleSubscribableMarker::class.starProjectedType)
            }
            .mapNotNull { it.getter(viewModel) as? LifecycleSubscribable }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun onCreate() {
        lifecycleSubscribables.forEach { it.subscribe(activity, lifecycleOwner, fragmentManager) }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy() {
        lifecycleSubscribables.forEach { it.unsubscribe() }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume() {
        viewModel.didResume()
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun onPause() {
        viewModel.didPause()
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.viewmodel

import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.viewModelScope

actual open class ViewModel internal actual constructor(allowFreezing: Boolean) : androidx.lifecycle.ViewModel() {

    actual val coroutineScope = viewModelScope

    actual override fun onCleared() {
        super.onCleared()
    }
}

/**
 * Binds an [AppCompatActivity] to the [ViewModel] to manage the viewmodel lifecycle.
 * @param activity The [AppCompatActivity] to bind to.
 */
fun <VM : BaseViewModel> VM.bind(activity: AppCompatActivity) {
    activity.lifecycle.addObserver(KalugaViewModelLifecycleObserver(this, activity, activity, activity.supportFragmentManager))
}

/**
 * Binds a [Fragment] to the [ViewModel] to manage the viewmodel lifecycle
 * @param fragment The [Fragment] to bind to.
 * @return `true` if the ViewModel could be bound to the [Fragment].
 */
fun <VM : BaseViewModel> VM.bind(fragment: Fragment): Boolean {
    val fragmentManager = fragment.fragmentManager ?: return false
    fragment.lifecycle.addObserver(KalugaViewModelLifecycleObserver(this, fragment.activity, fragment.viewLifecycleOwner, fragmentManager))
    return true
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.lifecycle

import com.splendo.kaluga.architecture.viewmodel.ViewModel

/**
 * Implementing this interface as a public property of a [ViewModel] allows for automatic binding to platform specific lifecycle aware manager.
 *
 * Be aware that classes implementing this interface that reside on a ViewModel as a property will have their getter invoked on instantiating the ViewModel to check if the instance is lifecycle aware (if the platform supports this).
 * This means, for example, that `lazy` delegation will have little effect.
 */
interface LifecycleSubscribableMarker

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.navigation

import com.splendo.kaluga.base.utils.Date
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json

/**
 * Value of a [NavigationBundleSpecRow]
 */
sealed class NavigationBundleValue<T> {

    abstract val value: T

    object UnitValue : NavigationBundleValue<Unit>() {
        override val value: Unit = Unit
    }
    data class BooleanValue internal constructor(override val value: Boolean) : NavigationBundleValue<Boolean>()
    data class BooleanArrayValue internal constructor(override val value: BooleanArray) : NavigationBundleValue<BooleanArray>() {
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other == null || this::class != other::class) return false

            other as BooleanArrayValue

            return value.contentEquals(other.value)
        }

        override fun hashCode(): Int {
            return value.contentHashCode()
        }
    }

    data class ByteValue internal constructor(override val value: Byte) : NavigationBundleValue<Byte>()
    data class ByteArrayValue internal constructor(override val value: ByteArray) : NavigationBundleValue<ByteArray>() {
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other == null || this::class != other::class) return false

            other as ByteArrayValue

            return value.contentEquals(other.value)
        }

        override fun hashCode(): Int {
            return value.contentHashCode()
        }
    }
    data class BundleValue<R : NavigationBundleSpecRow<*>> internal constructor(override val value: NavigationBundle<R>) : NavigationBundleValue<NavigationBundle<R>>()
    data class CharValue internal constructor(override val value: Char) : NavigationBundleValue<Char>()
    data class CharArrayValue internal constructor(override val value: CharArray) : NavigationBundleValue<CharArray>() {
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other == null || this::class != other::class) return false

            other as CharArrayValue

            return value.contentEquals(other.value)
        }

        override fun hashCode(): Int {
            return value.contentHashCode()
        }
    }

    data class CharSequenceValue internal constructor(override val value: CharSequence) : NavigationBundleValue<CharSequence>()
    data class DoubleValue internal constructor(override val value: Double) : NavigationBundleValue<Double>()
    data class DoubleArrayValue internal constructor(override val value: DoubleArray) : NavigationBundleValue<DoubleArray>() {
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other == null || this::class != other::class) return false

            other as DoubleArrayValue

            return value.contentEquals(other.value)
        }

        override fun hashCode(): Int {
            return value.contentHashCode()
        }
    }

    data class FloatValue internal constructor(override val value: Float) : NavigationBundleValue<Float>()
    data class FloatArrayValue internal constructor(override val value: FloatArray) : NavigationBundleValue<FloatArray>() {
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other == null || this::class != other::class) return false

            other as FloatArrayValue

            return value.contentEquals(other.value)
        }

        override fun hashCode(): Int {
            return value.contentHashCode()
        }
    }

    data class IntegerValue internal constructor(override val value: Int) : NavigationBundleValue<Int>()
    data class IntegerArrayValue internal constructor(override val value: IntArray) : NavigationBundleValue<IntArray>() {
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other == null || this::class != other::class) return false

            other as IntegerArrayValue

            return value.contentEquals(other.value)
        }

        override fun hashCode(): Int {
            return value.contentHashCode()
        }
    }

    data class LongValue internal constructor(override val value: Long) : NavigationBundleValue<Long>()
    data class LongArrayValue internal constructor(override val value: LongArray) : NavigationBundleValue<LongArray>() {
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other == null || this::class != other::class) return false

            other as LongArrayValue

            return value.contentEquals(other.value)
        }

        override fun hashCode(): Int {
            return value.contentHashCode()
        }
    }

    data class SerializedValue<T> internal constructor(val serializer: KSerializer<T>, override val value: T) : NavigationBundleValue<T>() {

        companion object {
            private val json = Json {}
        }

        val valueString: String = json.encodeToString(serializer, value)
    }

    data class ShortValue internal constructor(override val value: Short) : NavigationBundleValue<Short>()
    data class ShortArrayValue internal constructor(override val value: ShortArray) : NavigationBundleValue<ShortArray>() {
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other == null || this::class != other::class) return false

            other as ShortArrayValue

            return value.contentEquals(other.value)
        }

        override fun hashCode(): Int {
            return value.contentHashCode()
        }
    }

    data class StringValue internal constructor(override val value: String) : NavigationBundleValue<String>()
    data class StringArrayValue internal constructor(override val value: List<String>) : NavigationBundleValue<List<String>>()

    data class DateValue internal constructor(override val value: Date) : NavigationBundleValue<Date>()
    data class DateArrayValue internal constructor(override val value: List<Date>) : NavigationBundleValue<List<Date>>()

    data class OptionalValue<T> internal constructor(val optionalValue: NavigationBundleValue<T>?) : NavigationBundleValue<T?>() {
        override val value: T? = optionalValue?.value
    }
}

class NavigationBundleGetError : Exception()

/**
 * A container class that bundles a set of data for sharing between classes. Only takes values in a [NavigationBundleSpec] [R]
 */
class NavigationBundle<R : NavigationBundleSpecRow<*>> internal constructor(val spec: NavigationBundleSpec<R>, val values: Map<R, NavigationBundleValue<*>>) {

    /**
     * Tries to get data of a given [NavigationBundleSpecRow] from this bundle
     * @param row The [NavigationBundleSpecRow] for which to get the value from the bundle
     * @return The value for the [row] according to its type
     * @throws [NavigationBundleGetError] if the [row] does not match the [spec]
     */
    fun <V, S : NavigationBundleSpecRow<V>> get(row: S): V {
        row as? R ?: throw NavigationBundleGetError()
        val value = values[row] as? NavigationBundleValue<V> ?: throw NavigationBundleGetError()
        return value.value
    }

    /**
     * Convenience getter for a [NavigationBundle] described using a [SingleValueNavigationSpec]
     * @param type The [NavigationBundleSpecType] associated with the [SingleValueNavigationSpec]
     * @return The value stored in this bundle according to its type
     * @throws [NavigationBundleGetError] if [spec] is note [SingleValueNavigationSpec] associated with [type]
     */
    fun <V> get(type: NavigationBundleSpecType<V>) = get(SingleValueNavigationSpec.Row(type))
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.navigation

import com.splendo.kaluga.base.utils.Date
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json

/**
 * A row within a [NavigationBundleSpec] that is associated with a given [NavigationBundleSpecType]
 * @param associatedType The [NavigationBundleSpecType] associated with this row
 */
open class NavigationBundleSpecRow<T>(val associatedType: NavigationBundleSpecType<T>) {
    /**
     * Key for this row. Used for converting rows to data types.
     * Defaults to the name of the row, but can be overwritten if required
     */
    open val key: String? get() { return this::class.simpleName }

    /**
     * Converts a given value to the [NavigationBundleValue] associated with this row
     * @param value The value to convert
     * @return The [NavigationBundleValue] associated with the value
     */
    fun convertValue(value: T): NavigationBundleValue<T> = associatedType.convertValue(value)
}

/**
 * Types of data a [NavigationBundleSpecRow] can be associated with
 */
sealed class NavigationBundleSpecType<T> {

    /**
     * Converts a given value to the [NavigationBundleValue] associated with this type
     * @param value The value to convert
     * @return The [NavigationBundleValue] associated with the value
     */
    abstract fun convertValue(value: T): NavigationBundleValue<T>

    object UnitType : NavigationBundleSpecType<Unit>() {
        override fun convertValue(value: Unit): NavigationBundleValue<Unit> {
            return NavigationBundleValue.UnitValue
        }
    }

    object BooleanType : NavigationBundleSpecType<Boolean>() {

        override fun convertValue(value: Boolean): NavigationBundleValue<Boolean> {
            return NavigationBundleValue.BooleanValue(value)
        }
    }

    object BooleanArrayType : NavigationBundleSpecType<BooleanArray>() {
        override fun convertValue(value: BooleanArray): NavigationBundleValue<BooleanArray> {
            return NavigationBundleValue.BooleanArrayValue(value)
        }
    }

    object ByteType : NavigationBundleSpecType<Byte>() {
        override fun convertValue(value: Byte): NavigationBundleValue<Byte> {
            return NavigationBundleValue.ByteValue(value)
        }
    }
    class BundleType<R : NavigationBundleSpecRow<*>> internal constructor(val spec: NavigationBundleSpec<R>) : NavigationBundleSpecType<NavigationBundle<R>>() {
        override fun convertValue(value: NavigationBundle<R>): NavigationBundleValue<NavigationBundle<R>> {
            return NavigationBundleValue.BundleValue(value)
        }
    }

    object ByteArrayType : NavigationBundleSpecType<ByteArray>() {
        override fun convertValue(value: ByteArray): NavigationBundleValue<ByteArray> {
            return NavigationBundleValue.ByteArrayValue(value)
        }
    }

    object CharType : NavigationBundleSpecType<Char>() {
        override fun convertValue(value: Char): NavigationBundleValue<Char> {
            return NavigationBundleValue.CharValue(value)
        }
    }

    object CharArrayType : NavigationBundleSpecType<CharArray>() {
        override fun convertValue(value: CharArray): NavigationBundleValue<CharArray> {
            return NavigationBundleValue.CharArrayValue(value)
        }
    }

    object CharSequenceType : NavigationBundleSpecType<CharSequence>() {
        override fun convertValue(value: CharSequence): NavigationBundleValue<CharSequence> {
            return NavigationBundleValue.CharSequenceValue(value)
        }
    }

    object DoubleType : NavigationBundleSpecType<Double>() {
        override fun convertValue(value: Double): NavigationBundleValue<Double> {
            return NavigationBundleValue.DoubleValue(value)
        }
    }

    object DoubleArrayType : NavigationBundleSpecType<DoubleArray>() {
        override fun convertValue(value: DoubleArray): NavigationBundleValue<DoubleArray> {
            return NavigationBundleValue.DoubleArrayValue(value)
        }
    }

    object FloatType : NavigationBundleSpecType<Float>() {
        override fun convertValue(value: Float): NavigationBundleValue<Float> {
            return NavigationBundleValue.FloatValue(value)
        }
    }

    object FloatArrayType : NavigationBundleSpecType<FloatArray>() {
        override fun convertValue(value: FloatArray): NavigationBundleValue<FloatArray> {
            return NavigationBundleValue.FloatArrayValue(value)
        }
    }

    object IntegerType : NavigationBundleSpecType<Int>() {
        override fun convertValue(value: Int): NavigationBundleValue<Int> {
            return NavigationBundleValue.IntegerValue(value)
        }
    }

    object IntegerArrayType : NavigationBundleSpecType<IntArray>() {
        override fun convertValue(value: IntArray): NavigationBundleValue<IntArray> {
            return NavigationBundleValue.IntegerArrayValue(value)
        }
    }

    object LongType : NavigationBundleSpecType<Long>() {
        override fun convertValue(value: Long): NavigationBundleValue<Long> {
            return NavigationBundleValue.LongValue(value)
        }
    }

    object LongArrayType : NavigationBundleSpecType<LongArray>() {
        override fun convertValue(value: LongArray): NavigationBundleValue<LongArray> {
            return NavigationBundleValue.LongArrayValue(value)
        }
    }
    data class SerializedType<T>(private val serializer: KSerializer<T>) : NavigationBundleSpecType<T>() {
        companion object {
            private val json = Json { }
        }

        override fun convertValue(value: T): NavigationBundleValue<T> {
            return NavigationBundleValue.SerializedValue(serializer, value)
        }

        fun generateValue(stringValue: String): NavigationBundleValue<T>? {
            return try {
                NavigationBundleValue.SerializedValue(serializer, json.decodeFromString(serializer, stringValue))
            } catch (e: SerializationException) {
                null
            }
        }
    }

    object ShortType : NavigationBundleSpecType<Short>() {
        override fun convertValue(value: Short): NavigationBundleValue<Short> {
            return NavigationBundleValue.ShortValue(value)
        }
    }

    object ShortArrayType : NavigationBundleSpecType<ShortArray>() {
        override fun convertValue(value: ShortArray): NavigationBundleValue<ShortArray> {
            return NavigationBundleValue.ShortArrayValue(value)
        }
    }

    object StringType : NavigationBundleSpecType<String>() {
        override fun convertValue(value: String): NavigationBundleValue<String> {
            return NavigationBundleValue.StringValue(value)
        }
    }

    object StringArrayType : NavigationBundleSpecType<List<String>>() {
        override fun convertValue(value: List<String>): NavigationBundleValue<List<String>> {
            return NavigationBundleValue.StringArrayValue(value)
        }
    }

    object DateType : NavigationBundleSpecType<Date>() {
        override fun convertValue(value: Date): NavigationBundleValue<Date> {
            return NavigationBundleValue.DateValue(value)
        }
    }

    object DateArrayType : NavigationBundleSpecType<List<Date>>() {
        override fun convertValue(value: List<Date>): NavigationBundleValue<List<Date>> {
            return NavigationBundleValue.DateArrayValue(value)
        }
    }

    data class OptionalType<T>(val type: NavigationBundleSpecType<T>) : NavigationBundleSpecType<T?>() {
        override fun convertValue(value: T?): NavigationBundleValue<T?> {
            return value?.let {
                NavigationBundleValue.OptionalValue(type.convertValue(it))
            } ?: NavigationBundleValue.OptionalValue(null)
        }
    }
}

/**
 * A set of [NavigationBundleSpecRow]s that can be used to form a [NavigationBundle] using [NavigationBundleSpec.toBundle]
 */
open class NavigationBundleSpec<R : NavigationBundleSpecRow<*>>(val rows: Set<R>)

/**
 * A [NavigationBundleSpec] that only provides a single [NavigationBundleSpecRow]
 * @param type The [NavigationBundleSpecType] used for the [NavigationBundleSpecRow]
 */
class SingleValueNavigationSpec<T>(type: NavigationBundleSpecType<T>) : NavigationBundleSpec<SingleValueNavigationSpec.Row<T>>(setOf(Row(type))) {
    data class Row<T>(val type: NavigationBundleSpecType<T>) : NavigationBundleSpecRow<T>(type)
}

/**
 * Creates a [NavigationBundle] from the [NavigationBundleSpec]
 * @param mapper Function mapping the [NavigationBundleSpecRow] in this spec to their respective [NavigationBundleValue]
 * @return A [NavigationBundle] matching this [NavigationBundleSpec]
 */
fun <R : NavigationBundleSpecRow<*>> NavigationBundleSpec<R>.toBundle(mapper: (R) -> NavigationBundleValue<*>): NavigationBundle<R> {
    return NavigationBundle(this, rows.associateWith { mapper.invoke(it) })
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.navigation

/**
 * Action that describes the intent to navigate
 * @param bundle The [NavigationBundle] containing data used to configure navigation
 */
open class NavigationAction<B : NavigationBundleSpecRow<*>>(val bundle: NavigationBundle<B>?)

/**
 * A [NavigationAction] that has a [SingleValueNavigationSpec] bundle
 * @param value The value passed by the action
 * @param type The [NavigationBundleSpecType] describing the object passed by the action
 */
open class SingleValueNavigationAction<T>(
    val value: T,
    val type: NavigationBundleSpecType<T>
) : NavigationAction<SingleValueNavigationSpec.Row<T>>(
    SingleValueNavigationSpec(type).toBundle {
        it.convertValue(value)
    }
)

/**
 * Class that can trigger a given [NavigationAction]
 */
expect interface Navigator<A : NavigationAction<*>> {
    /**
     * Triggers a given [NavigationAction]
     * @param action The [NavigationAction] to trigger
     */
    fun navigate(action: A)
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.

 */

@file:JvmName("DisposableCommonKt")
package com.splendo.kaluga.architecture.observable

import co.touchlab.stately.collections.sharedMutableListOf
import co.touchlab.stately.concurrency.AtomicBoolean
import co.touchlab.stately.ensureNeverFrozen
import co.touchlab.stately.isFrozen
import kotlin.jvm.JvmName

typealias DisposeHandler = () -> Unit

internal expect fun <R : T, T, OO : ObservableOptional<R>> addObserver(observation: Observation<R, T, OO>, observer: (R) -> Unit)

internal expect fun <R : T, T, OO : ObservableOptional<R>> removeObserver(observation: Observation<R, T, OO>, observer: (R) -> Unit)

internal expect fun <R : T, T, OO : ObservableOptional<R>> observers(observation: Observation<R, T, OO>): List<(R) -> Unit>

/**
 * Reference to an object that should be disposed in time
 */
interface Disposable {
    /**
     * Disposes the associated object
     */
    fun dispose()

    /**
     * Adds this disposable to a [DisposeBag]
     */
    fun addTo(disposeBag: DisposeBag)
}

expect class SimpleDisposable(onDispose: DisposeHandler) : BaseSimpleDisposable

/**
 * Plain [Disposable] to an object that should be disposed in time
 * @param onDispose Function to call when disposing the object
 */
abstract class BaseSimpleDisposable(onDispose: DisposeHandler) : Disposable {

    private val _isDisposed = AtomicBoolean(false)
    val isDisposed
        get() = _isDisposed.value

    private var disposeHandler: DisposeHandler? = onDispose

    /**
     * Disposes the associated object
     */
    override fun dispose() {
        if (_isDisposed.compareAndSet(expected = false, new = true)) {
            disposeHandler?.invoke()
            if (!disposeHandler.isFrozen) disposeHandler = null
            afterDispose()
        }
    }

    protected open fun afterDispose() {}

    /**
     * Adds this disposable to a [DisposeBag]
     */
    override fun addTo(disposeBag: DisposeBag) {
        disposeBag.add(this)
    }
}

/**
 * Container for multiple [Disposable]. Allows nested [DisposeBag].
 */
class DisposeBag(allowFreezing: Boolean = false) : Disposable {

    constructor() : this(false)

    private val disposables: MutableList<Disposable> = if (allowFreezing) sharedMutableListOf() else mutableListOf()
    private val nestedBags: MutableList<DisposeBag> = if (allowFreezing) sharedMutableListOf() else mutableListOf()

    init {
        if (!allowFreezing)
            ensureNeverFrozen() // if our DisposeBag gets frozen we cannot dispose properly so this is generally unwanted
    }

    /**
     * Adds a nested [DisposeBag]
     */
    fun add(disposeBag: DisposeBag) {
        nestedBags.add(disposeBag)
    }

    /**
     * Adds a [Disposable] to this [DisposeBag]
     */
    fun add(disposable: Disposable) {
        disposables.add(disposable)
    }

    override fun addTo(disposeBag: DisposeBag) {
        disposeBag.add(this)
    }

    /**
     * Disposes all [Disposable]s and nested [DisposeBag]s added to this [DisposeBag].
     * Added elements can only be disposed once
     */
    override fun dispose() {
        disposables.forEach { it.dispose() }
        disposables.clear()
        nestedBags.forEach { it.dispose() }
        nestedBags.clear()
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.observable

import co.touchlab.stately.concurrency.AtomicBoolean
import co.touchlab.stately.concurrency.AtomicReference
import co.touchlab.stately.isFrozen
import com.splendo.kaluga.architecture.observable.ObservableOptional.Nothing
import com.splendo.kaluga.architecture.observable.ObservableOptional.Value
import com.splendo.kaluga.base.isOnMainThread
import com.splendo.kaluga.base.runBlocking
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

inline fun <reified R : T, T> ObservableOptional<T>.resultValueOrDefault(defaultValue: R): R =
    try {
        valueOrNull?.let { it as? R } ?: defaultValue
    } catch (e: ClassCastException) {
        defaultValue
    }

fun <R : T, T> ObservableOptional<T>.asResult(defaultValue: Value<R>?): ObservableOptional<R> =
    try {
        if (this is Value<*> && (this.value != null || defaultValue == null))
            Value(this.value as R)
        else
            defaultValue ?: Nothing()
    } catch (e: ClassCastException) {
        defaultValue ?: Nothing()
    }

/**
 * Result type for an [BaseObservable]. Used to allow for the distinction between `null` and optional values
 */
sealed class ObservableOptional<T> : ReadOnlyProperty<Any?, T?> {

    abstract val valueOrNull: T?

    /**
     * The [BaseObservable] has a result, this result could be `null`
     */
    data class Value<T>(val value: T) : ObservableOptional<T>() {

        override fun getValue(thisRef: Any?, property: KProperty<*>): T = value
        override fun toString(): String = "Value[$value]"
        override val valueOrNull: T? = value
    }

    /**
     * The Observable does not have a result
     */
    class Nothing<T> : ObservableOptional<T>() {
        override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
            return null
        }

        override val valueOrNull: T? = null
        override fun toString(): String = "Nothing"

        override fun equals(other: Any?): Boolean {
            return other is Nothing<*>
        }

        override fun hashCode(): Int {
            return this::class.hashCode()
        }
    }
}

open class ObservationDefault<R : T?, T>(
    override val defaultValue: Value<R>,
    override val initialValue: Value<T?>
) : Observation<R, T?, Value<R>>(initialValue),
    ReadWriteProperty<Any?, R>,
    MutableDefaultInitialized<R, T?> {
    constructor(
        defaultValue: R,
        initialValue: Value<T?>
    ) : this(Value(defaultValue), initialValue)

    override fun getValue(thisRef: Any?, property: KProperty<*>): R = current
    override fun setValue(thisRef: Any?, property: KProperty<*>, value: R) {
        observedValue = Value(value)
    }

    override val current: R
        get() = currentObserved.value

    override val stateFlow: MutableStateFlow<R> by lazy {
        MutableStateFlow(current).also { stateFlow ->
            this.observeInitialized {
                stateFlow.value = it
            }
        }
    }

    override val valueDelegate: ReadWriteProperty<Any?, R>
        get() = this

    override fun observeInitialized(onNext: (R) -> Unit): Disposable {
        return observe { onNext(it as R) }
    }
}

class ObservationUninitialized<T> :
    Observation<T, T, ObservableOptional<T>>(Nothing()),
    MutableUninitialized<T>,
    ReadWriteProperty<Any?, T?> {

    override val initialValue: Nothing<T> = Nothing()

    override fun getValue(thisRef: Any?, property: KProperty<*>): T? =
        currentObserved.valueOrNull

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
        observedValue = value?.let { Value(value) } ?: Nothing()
    }

    override val stateFlow: MutableStateFlow<T?> by lazy {
        MutableStateFlow(currentOrNull).also { stateFlow ->
            observe { stateFlow.value = it }
        }
    }
    override val valueDelegate: ReadWriteProperty<Any?, T?>
        get() = this
}

open class ObservationInitialized<T>(
    override val initialValue: Value<T>
) : Observation<T, T, Value<T>>(initialValue),
    ReadWriteProperty<Any?, T>,
    MutableInitialized<T, T> {

    constructor(initialValue: T) : this(Value(initialValue))

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        observedValue = Value(value)
    }

    override fun getValue(thisRef: Any?, property: KProperty<*>): T = current
    override val current: T
        get() = currentObserved.value

    override val stateFlow: MutableStateFlow<T> by lazy {
        MutableStateFlow(current).also { stateFlow ->
            observeInitialized { stateFlow.value = it }
        }
    }

    override fun observeInitialized(onNext: (T) -> Unit): Disposable {
        return observe { onNext(it as T) }
    }

    override val valueDelegate: ReadWriteProperty<Any?, T>
        get() = this
}

open class Observation<R : T, T, OO : ObservableOptional<R>>(
    override val initialValue: ObservableOptional<T>
) : Initial<R, T> {

    // this is not used by iOS
    internal val observers by lazy { mutableListOf<(R) -> Unit>() }

    open val defaultValue: Value<R>? = null

    var onFirstObservation: (() -> Unit)? = null
        set(value) {
            // if an observation took place before this field was set, invoke immediately
            if (firstObservation.value) {
                value?.invoke()
            } else {
                field = value
            }
        }

    private val firstObservation = AtomicBoolean(false)

    var beforeObservedValueGet: ((OO) -> ObservableOptional<T>)? = null

    // alternate implementation with freezing references

    // This implementation's withContext below should freeze this reference if it's accessed from another thread
    // but if all access is from the main thread it should not be frozen.
    // if it does get frozen, instead the atomic reference is used to update into.

    @Suppress("SENSELESS_COMPARISON") // if not initialized this can still happen
    private var backingInternalValue: ObservableOptional<R>? = null
        get() =
            field ?: if (initialValue == null) // "lazy" var
                throw RuntimeException("Observing before class is initialized. Are you observing from the constructor of your observable?")
            else
                initialValue.asResult(defaultValue)
        set(value) {
            checkNotNull(value) { "internal value can not be set to null" }
            field = value
        }

    private val backingAtomicReference = AtomicReference<ObservableOptional<R>?>(null)

    @Suppress("UNUSED_VALUE", "UNCHECKED_CAST") // should always downcast as R extends T
    /**
     * Normally you set a value assigning [observedValue],
     * however if you are sure you are in [Dispatchers.Main] this method can be called directly
     *
     * @param value the new value that will be observed
     */
    fun setValueUnconfined(value: ObservableOptional<T>): ObservableOptional<T> {
        val v = value.asResult(defaultValue)
        val before = backingAtomicReference.get() ?: backingInternalValue

        if (before != v) {
            // TODO: since our context is supposed to be single thread, we could use a @ThreadLocal similar to tracking observers
            // This would only need to freeze the current result, or if possible a copy of it

            if (!this@Observation.isFrozen) // if the parent is frozen we can no longer update this reference
                backingInternalValue = v
            else // if it's frozen use an atomic value
                backingAtomicReference.set(v)

            val result = (v as? Value<*>)?.value as R

            observers(this@Observation).forEach {
                it(result)
            }
            return v as ObservableOptional<T>
        }
        return before as ObservableOptional<T>
    }

    private fun getValue(): OO {
        if (firstObservation.compareAndSet(expected = false, new = true)) {
            onFirstObservation?.invoke()
        }

        return handleOnMain {
            backingAtomicReference.get() ?: backingInternalValue ?: error("unexpected null")
        } as OO
    }

    /**
     * Adds an observing function to the Observable to be notified on each change to the observable.
     * If there is a current value, there will be an immediate notification
     *
     * @param onNext Function to be called each time the value of the Observable changes
     * @return [Disposable] that removes the observing function when disposed
     */
    override fun observe(onNext: (R?) -> Unit): Disposable = handleOnMain {
        @Suppress("UNCHECKED_CAST") // OO<T> should be guaranteed
        // send the value before adding
        val lastResult = currentObserved
        onNext((lastResult as? Value<*>)?.value as R)
        addObserver(this@Observation, onNext)
        // adding an observer often happens concurrently with initialization,
        // if we detect a change in the current observed value we re-send it to the added observer
        val newResult = currentObserved
        if (newResult != lastResult)
            onNext((newResult as? Value<*>)?.value as R)
        SimpleDisposable {
            handleOnMain {
                removeObserver(this@Observation, onNext)
            }
        }
    }

    val currentObserved: OO
        get() = observedValue as OO

    @Suppress("UNCHECKED_CAST")
    var observedValue: ObservableOptional<T>
        // use getter and setter to avoid lazy initialization of observable
        get() {
            beforeObservedValueGet?.let { beforeObservedValueGet ->
                val current = getValue()
                val new = beforeObservedValueGet(current)
                return if (new != current) // state not events
                    handleOnMain {
                        setValueUnconfined(new)
                    }
                else new
            }
            return getValue() as ObservableOptional<T>
        }
        set(v) {
            handleOnMain {
                setValueUnconfined(v)
            }
        }

    override val currentOrNull: R?
        get() = currentObserved.valueOrNull

    private fun <T> handleOnMain(block: () -> T): T = if (isOnMainThread) {
        block()
    } else {
        runBlocking(Dispatchers.Main) {
            block()
        }
    }
}

/**
 * An observable value.
 *
 * The value that can be observed is of the type [ObservableOptional].
 * This is used to distinguish between "no value" [ObservableOptional.Nothing] and Value [ObservableOptional.Value] (which could be null).
 *
 * An [initialValue] value can be set, which will remain until an actual value is observed.
 * The actual implementation of holding the observed value is done by [Observation].
 *
 * Observable also implements [ReadOnlyProperty] so it can act as a delegate for an [ObservableOptional] val
 */
abstract class BaseObservable<R : T, T, OO : ObservableOptional<R>>(
    protected open val observation: Observation<R, T, OO>
) : BasicObservable<R, T, OO>,
    Initial<R, T> by observation {
    constructor(
        initialValue: ObservableOptional<T>,
    ) : this(Observation(initialValue))
}

class SimpleInitializedObservable<T>(
    initialValue: T
) : BaseInitializedObservable<T>(Value(initialValue))

@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
abstract class BaseInitializedObservable<T>(
    observation: ObservationInitialized<T>
) : BaseObservable<T, T, Value<T>>(observation),
    InitializedObservable<T>,
    Initialized<T, T> by observation {

    override fun getValue(thisRef: Any?, property: KProperty<*>): Value<T> =
        observation.currentObserved

    constructor(
        initialValue: Value<T>
    ) : this(ObservationInitialized(initialValue))
}

interface BasicObservable<R : T, T, OO : ObservableOptional<R>> :
    ReadOnlyProperty<Any?, OO>,
    Initial<R, T>

interface InitializedObservable<T> : BasicObservable<T, T, Value<T>>, Initialized<T, T>

interface UninitializedObservable<T> :
    BasicObservable<T, T, ObservableOptional<T>>,
    Uninitialized<T>

interface BasicSubject<R : T, T, OO : ObservableOptional<R>> :
    BasicObservable<R, T, OO>,
    SuspendableSetter<T> {
    fun bind(coroutineScope: CoroutineScope)
    fun bind(coroutineScope: CoroutineScope, context: CoroutineContext)
}

interface UninitializedSubject<T> :
    BasicSubject<T, T, ObservableOptional<T>>,
    UninitializedObservable<T>,
    MutableUninitialized<T>

interface InitializedSubject<T> :
    BasicSubject<T, T, Value<T>>,
    InitializedObservable<T>,
    MutableInitialized<T, T>

interface DefaultObservable<R : T?, T> :
    BasicObservable<R, T?, Value<R>>,
    DefaultInitialized<R, T?>

interface DefaultSubject<R : T?, T> :
    BasicSubject<R, T?, Value<R>>,
    DefaultObservable<R, T>,
    MutableDefaultInitialized<R, T?>

expect interface WithState<T> {
    val stateFlow: StateFlow<T>
    val valueDelegate: ReadOnlyProperty<Any?, T>
}

interface WithMutableState<T> : WithState<T> {
    override val stateFlow: MutableStateFlow<T>
    override val valueDelegate: ReadWriteProperty<Any?, T>
}

interface MutableDefaultInitialized<R : T?, T> : DefaultInitialized<R, T?>, WithMutableState<R>

interface DefaultInitialized<R : T?, T> : Initialized<R, T?>, WithState<R> {
    val defaultValue: Value<R>
}

interface MutableUninitialized<T> : Uninitialized<T>, WithMutableState<T?>
interface Uninitialized<T> : Initial<T, T>, WithState<T?> {
    override val initialValue: Nothing<T>
}

interface MutableInitialized<R : T, T> : Initialized<R, T>, WithMutableState<R>
interface Initialized<R : T, T> : Initial<R, T>, WithState<R> {
    override val initialValue: Value<T>
    val current: R
    fun observeInitialized(onNext: (R) -> Unit): Disposable
}

interface Initial<R : T, T> {
    val initialValue: ObservableOptional<T>
    val currentOrNull: T?
    fun observe(onNext: (R?) -> Unit): Disposable
}

interface SuspendableSetter<T> : Postable<T> {
    /**
     * Updates the value of this [Postable]
     * @param newValue The new value of the postable
     */
    suspend fun set(newValue: T)
}

/**
 * A Postable can handle values being posted to it.
 */
interface Postable<T> {
    /**
     * Updates the value of this [Postable]
     * @param newValue The new value of the postable
     */

    fun post(newValue: T)
}

@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") // we want our delegate to override
abstract class BaseDefaultObservable<R : T?, T>(
    override val observation: ObservationDefault<R, T?>
) :
    BaseObservable<R, T?, Value<R>>(observation),
    DefaultObservable<R, T?>,
    DefaultInitialized<R, T?> by observation {
    constructor(
        defaultValue: R,
        initialValue: Value<T?>
    ) : this(ObservationDefault<R, T?>(Value(defaultValue), initialValue))

    override fun getValue(thisRef: Any?, property: KProperty<*>): Value<R> =
        observation.currentObserved
}

/**
 * A Subject is an [BaseObservable] with a value that can be changed using the [post] method from the [Postable] interface
 */
abstract class AbstractBaseSubject<R : T, T, OO : ObservableOptional<R>>(
    observation: Observation<R, T, OO>,
    private val stateFlowToBind: () -> StateFlow<R?>
) :
    BaseObservable<R, T, OO>(observation), BasicSubject<R, T, OO> {

    final override fun bind(coroutineScope: CoroutineScope) =
        bind(coroutineScope, Dispatchers.Main.immediate)

    override fun bind(coroutineScope: CoroutineScope, context: CoroutineContext) {
        coroutineScope.launch(context) {
            stateFlowToBind().collect { set(it as T) }
        }
    }
}

expect abstract class BaseSubject<R : T, T, OO : ObservableOptional<R>>(
    observation: Observation<R, T, OO>,
    stateFlowToBind: () -> StateFlow<R?>
) :
    AbstractBaseSubject<R, T, OO>

@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
abstract class AbstractBaseInitializedSubject<T>(override val observation: ObservationInitialized<T>) :
    BaseSubject<T, T, Value<T>>(
        observation,
        { observation.stateFlow }
    ),
    InitializedSubject<T>,
    MutableInitialized<T, T> by observation {

    constructor(
        initialValue: Value<T>,
    ) : this(ObservationInitialized(initialValue))

    override fun getValue(thisRef: Any?, property: KProperty<*>): Value<T> =
        observation.currentObserved
}

expect abstract class BaseInitializedSubject<T>(observation: ObservationInitialized<T>) :
    AbstractBaseInitializedSubject<T> {
    constructor(
        initialValue: Value<T>
    )
}

@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
abstract class AbstractBaseUninitializedSubject<T>(
    override val observation: ObservationUninitialized<T>
) : BaseSubject<T, T, ObservableOptional<T>>(
    observation,
    { observation.stateFlow }
),
    UninitializedSubject<T>,
    MutableUninitialized<T> by observation {
    override fun getValue(thisRef: Any?, property: KProperty<*>): ObservableOptional<T> =
        observation.observedValue
}

expect abstract class BaseUninitializedSubject<T>(
    observation: ObservationUninitialized<T>
) : AbstractBaseUninitializedSubject<T>

@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
abstract class BaseUninitializedObservable<T>(
    override val observation: ObservationUninitialized<T>
) : BaseObservable<T, T, ObservableOptional<T>>(
    observation
),
    UninitializedObservable<T>,
    Uninitialized<T> by observation {

    override fun getValue(thisRef: Any?, property: KProperty<*>): ObservableOptional<T> =
        observation.currentObserved
}

@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE")
class SimpleInitializedSubject<T>(
    override val observation: ObservationInitialized<T>
) : BaseInitializedSubject<T>(
    observation
) {

    constructor(
        initialValue: T
    ) : this(Value(initialValue))

    constructor(
        initialValue: Value<T>
    ) : this(ObservationInitialized(initialValue))

    override suspend fun set(newValue: T): Unit = withContext(Dispatchers.Main.immediate) {
        observation.setValueUnconfined(Value(newValue))
    }

    override fun post(newValue: T) {
        observation.observedValue = Value(newValue)
    }
}

class SimpleDefaultSubject<R : T?, T>(
    defaultValue: R,
    initialValue: T? = defaultValue
) :
    BaseDefaultSubject<R, T?>(
        Value(defaultValue),
        Value(initialValue)
    ) {

    override fun post(newValue: T?) {
        observation.observedValue = Value(newValue)
    }

    override suspend fun set(newValue: T?) = withContext<Unit>(Dispatchers.Main.immediate) {
        observation.setValueUnconfined(Value(newValue))
    }
}

@Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") // deliberate
abstract class AbstractBaseDefaultSubject<R : T?, T>(
    override val observation: ObservationDefault<R, T?>
) : BaseSubject<R, T?, Value<R>>(
    observation,
    { observation.stateFlow }
),
    DefaultSubject<R, T>,
    MutableDefaultInitialized<R, T?> by observation {
    constructor(
        defaultValue: Value<R>,
        initialValue: Value<T?>
    ) : this(observation = ObservationDefault<R, T?>(defaultValue, initialValue))

    override fun getValue(thisRef: Any?, property: KProperty<*>): Value<R> =
        observation.currentObserved
}

expect abstract class BaseDefaultSubject<R : T?, T>(
    observation: ObservationDefault<R, T?>
) : AbstractBaseDefaultSubject<R, T> {

    constructor(
        defaultValue: Value<R>,
        initialValue: Value<T?>
    )
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.observable

import com.splendo.kaluga.architecture.observable.ObservableOptional.*
import com.splendo.kaluga.base.flow.HotFlowable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty

fun <R : T, T, OO : ObservableOptional<R>> observeFlow(
    observation: Observation<R, T, OO>,
    coroutineScope: CoroutineScope,
    context: CoroutineContext,
    flow: Flow<T>
) {

    observation.onFirstObservation = {
        coroutineScope.launch(context) {
            flow.flowOn(Dispatchers.Main.immediate).collect { // shortcut to reuse the scope we are collecting in
                @Suppress("UNCHECKED_CAST") // should always be correct
                observation.setValueUnconfined(Value(it))
            }
        }
    }
}

class DefaultFlowObservable<R : T?, T>(
    defaultValue: R,
    initialValue: T? = defaultValue,
    coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate,
    flow: Flow<T?>,
    observation: ObservationDefault<R, T?> = ObservationDefault(defaultValue, Value(initialValue)),
) : BaseDefaultObservable<R, T?>(observation) {
    init {
        observeFlow(observation, coroutineScope, context, flow)
    }
}

class FlowObservable<T>(
    val coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate,
    val flow: Flow<T>,
    observation: ObservationUninitialized<T> = ObservationUninitialized()
) : BaseUninitializedObservable<T>(observation) {
    init {
        observeFlow(observation, coroutineScope, context, flow)
    }
}

class FlowInitializedObservable<T>(
    initialValue: T,
    val coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate,
    val flow: Flow<T>,
    observation: ObservationInitialized<T> = ObservationInitialized(initialValue)
) : BaseInitializedObservable<T>(observation) {
    init {
        observeFlow(observation, coroutineScope, context, flow)
    }
}

class MutableFlowSubjectHelper<R : T, T, OO : ObservableOptional<R>>(
    val coroutineScope: CoroutineScope,
    val context: CoroutineContext,
    val flow: () -> Flow<T>,
    val setter: suspend (T) -> Unit,
    val poster: (T) -> Unit = { coroutineScope.launch(context) { setter(it) } },
    observation: Observation<R, T, OO>
) : SuspendableSetter<T> {

    init {
        observeFlow(observation, coroutineScope, context, flow())
    }

    override suspend fun set(newValue: T) = setter(newValue)
    override fun post(newValue: T) = poster(newValue)
}

class HotFlowableDefaultSubject<R : T?, T>(
    defaultValue: R,
    val coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate,
    hotFlowable: HotFlowable<T?>,
    autoBind: Boolean = true,
    observation: ObservationDefault<R, T?> = ObservationDefault(defaultValue, Value(hotFlowable.initialValue))
) : BaseDefaultSubject<R, T?>(observation),
    SuspendableSetter<T?> by MutableFlowSubjectHelper(
        coroutineScope,
        context,
        flow = { hotFlowable.flow() },
        setter = { hotFlowable.set(it) },
        observation = observation
    ) {
    init {
        if (autoBind) bind(coroutineScope, context)
    }
}

class HotFlowableInitializedSubject<T>(
    val coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate,
    hotFlowable: HotFlowable<T>,
    autoBind: Boolean = true,
    observation: ObservationInitialized<T> = ObservationInitialized(Value(hotFlowable.initialValue))
) : BaseInitializedSubject<T>(observation),
    SuspendableSetter<T> by MutableFlowSubjectHelper(
        coroutineScope,
        context,
        flow = { hotFlowable.flow() },
        setter = { hotFlowable.set(it) },
        observation = observation
    ) {
    init {
        if (autoBind) bind(coroutineScope, context)
    }
}

open class StateFlowSubject<T>(
    val coroutineScope: CoroutineScope,
    val context: CoroutineContext = Dispatchers.Main.immediate,
    private val observedStateFlow: MutableStateFlow<T>,
    autoBind: Boolean = true,
    observation: ObservationInitialized<T> = ObservationInitialized(Value(observedStateFlow.value)),
) : BaseInitializedSubject<T>(observation),
    SuspendableSetter<T> by MutableFlowSubjectHelper(
        coroutineScope,
        context,
        { observedStateFlow },
        { observedStateFlow.value = it },
        { observedStateFlow.value = it },
        observation
    ) {
    init {
        if (autoBind) bind(coroutineScope, context)
    }
}

open class StateFlowDefaultSubject<R : T?, T>(
    defaultValue: R,
    val coroutineScope: CoroutineScope,
    val context: CoroutineContext = Dispatchers.Main.immediate,
    private val observedStateFlow: MutableStateFlow<T?>,
    autoBind: Boolean = true,
    observation: ObservationDefault<R, T?> = ObservationDefault(defaultValue, Value(observedStateFlow.value)),
) : BaseDefaultSubject<R, T?>(observation),
    SuspendableSetter<T?> by MutableFlowSubjectHelper(
        coroutineScope,
        context,
        { observedStateFlow },
        { observedStateFlow.value = it },
        { observedStateFlow.value = it },
        observation
    ) {
    init {
        if (autoBind) bind(coroutineScope, context)
    }
}

/**
 * [BaseSubject] that synchronizes its value to a [MutableStateFlow]
 * @param observedStateFlow The [MutableStateFlow] to synchronize to
 * @param coroutineScope The [CoroutineScope] on which to observe changes to the [StateFlow]
 */
open class StateFlowInitializedSubject<T>(
    val coroutineScope: CoroutineScope,
    val context: CoroutineContext = Dispatchers.Main.immediate,
    private val observedStateFlow: MutableStateFlow<T>,
    autoBind: Boolean = true,
    observation: ObservationInitialized<T> = ObservationInitialized(Value(observedStateFlow.value)),
) : BaseInitializedSubject<T>(observation),
    SuspendableSetter<T> by MutableFlowSubjectHelper(
        coroutineScope,
        context,
        { observedStateFlow },
        { observedStateFlow.value = it },
        { observedStateFlow.value = it },
        observation
    ) {
    init {
        if (autoBind) bind(coroutineScope, context)
    }
}

open class SharedFlowDefaultSubject<R : T?, T>(
    defaultValue: R,
    initialValue: T? = defaultValue,
    coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate,
    sharedFlow: MutableSharedFlow<T?>,
    autoBind: Boolean = true,
    observation: ObservationDefault<R, T?> = ObservationDefault(defaultValue, Value(initialValue)),
) : BaseDefaultSubject<R, T?>(
    observation
),
    SuspendableSetter<T?> by MutableFlowSubjectHelper(
        coroutineScope,
        context,
        { sharedFlow },
        { sharedFlow.emit(it) },
        observation = observation
    ) {
    init {
        if (autoBind) bind(coroutineScope, context)
    }
}

open class SharedFlowSubject<T>(
    coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate,
    sharedFlow: MutableSharedFlow<T>,
    autoBind: Boolean = true,
    observation: ObservationUninitialized<T> = ObservationUninitialized()
) : BaseUninitializedSubject<T>(
    observation
),
    SuspendableSetter<T> by MutableFlowSubjectHelper(
        coroutineScope,
        context,
        { sharedFlow },
        { sharedFlow.emit(it) },
        observation = observation
    ) {
    init {
        if (autoBind) bind(coroutineScope, context)
    }
}

open class SharedFlowInitializedSubject<T>(
    initialValue: T,
    coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate,
    sharedFlow: MutableSharedFlow<T>,
    autoBind: Boolean = true,
    observation: ObservationInitialized<T> = ObservationInitialized(initialValue)
) : BaseInitializedSubject<T>(
    observation
),
    SuspendableSetter<T> by MutableFlowSubjectHelper(
        coroutineScope,
        context,
        { sharedFlow },
        { sharedFlow.emit(it) },
        observation = observation
    ) {
    init {
        if (autoBind) bind(coroutineScope, context)
    }
}

fun <R : T, T> readOnlyPropertyObservableHelper(readOnlyProperty: ReadOnlyProperty<Any?, T>, observation: Observation<R, T, Value<R>>) {

    val readOnlyValue by readOnlyProperty

    observation.beforeObservedValueGet = { current ->
        @Suppress("UnnecessaryVariable") val new = readOnlyValue // variable actually needed due to delegation
        if (new != current.valueOrNull)
            Value(new)
        else
            current as ObservableOptional<T>
    }
}

class ReadOnlyPropertyDefaultObservable<R : T, T>(
    defaultValue: R,
    readOnlyProperty: ReadOnlyProperty<Any?, T>,
    observation: ObservationDefault<R, T?> = ObservationDefault(
        defaultValue,
        Value(
            run {
                val v by readOnlyProperty
                v
            }
        )
    )
) :
    BaseDefaultObservable<R, T> (observation) { // no default is needed, since this is for use with non-optionals

    init {
        readOnlyPropertyObservableHelper(readOnlyProperty, observation)
    }
}

class ReadOnlyPropertyInitializedObservable<T>(
    readOnlyProperty: ReadOnlyProperty<Any?, T>,
    observation: ObservationInitialized<T> = ObservationInitialized(
        Value(
            run {
                val v by readOnlyProperty
                v
            }
        )
    )
) :
    BaseInitializedObservable<T> (observation), InitializedObservable<T> {

    init {
        readOnlyPropertyObservableHelper(readOnlyProperty, observation)
    }
}

class ReadWritePropertyObservableHelper<R : T, T>(
    readWriteProperty: ReadWriteProperty<Any?, T>,
    private val observation: Observation<R, T, Value<R>>,
) : SuspendableSetter<T> {

    private var readWriteValue by readWriteProperty

    init {
        observation.beforeObservedValueGet = { current ->
            val new = readWriteValue
            if (new != current.valueOrNull)
                Value(new)
            else
                current as ObservableOptional<T>
        }
    }

    override suspend fun set(newValue: T) = post(newValue)

    override fun post(newValue: T) {
        readWriteValue = newValue
        observation.observedValue = Value(newValue)
    }
}

/**
 * [Initialized] subject that matches its value to a [ReadWriteProperty].
 * While the subject updated the [ReadWriteProperty], changes to the property are not immediately delegated back to the subject.
 * Use a [Flow] based subject if synchronized values are required
 */
class ReadWritePropertyInitializedSubject<T>(
    readWriteProperty: ReadWriteProperty<Any?, T>,
    initialValue: Value<T> = Value(
        run {
            val v by readWriteProperty
            v
        }
    ),
    context: CoroutineContext,
    scope: CoroutineScope?,
    observation: ObservationInitialized<T> = ObservationInitialized(initialValue)
) :
    BaseInitializedSubject<T>(
        observation
    ),
    SuspendableSetter<T> by ReadWritePropertyObservableHelper(readWriteProperty, observation) {
    init {
        scope?.let { bind(it, context) }
    }
}

class ReadWritePropertyDefaultSubject<R : T?, T>(
    defaultValue: R,
    readWriteProperty: ReadWriteProperty<Any?, T?>,
    initialValue: Value<T?> = Value(
        run {
            val v by readWriteProperty
            v
        }
    ),
    context: CoroutineContext,
    scope: CoroutineScope?,
    observation: ObservationDefault<R, T?> = ObservationDefault(defaultValue, initialValue)
) :
    BaseDefaultSubject<R, T>(
        observation
    ),
    SuspendableSetter<T?> by ReadWritePropertyObservableHelper(readWriteProperty, observation) {
    init {
        scope?.let {
            bind(scope, context)
        }
    }
}

fun <R : T, T> ReadOnlyProperty<Any?, T?>.toDefaultObservable(defaultValue: R) =
    ReadOnlyPropertyDefaultObservable(defaultValue, this)

fun <T> ReadOnlyProperty<Any?, T>.toInitializedObservable() =
    ReadOnlyPropertyInitializedObservable(this)

fun <T> ReadWriteProperty<Any?, T>.toInitializedSubject(context: CoroutineContext = Dispatchers.Main.immediate, scope: CoroutineScope? = null) =
    ReadWritePropertyInitializedSubject(this, context = context, scope = scope)

fun <R : T, T> ReadWriteProperty<Any?, T?>.toDefaultSubject(defaultValue: R, context: CoroutineContext = Dispatchers.Main.immediate, scope: CoroutineScope? = null): ReadWritePropertyDefaultSubject<R, T?> =
    ReadWritePropertyDefaultSubject(defaultValue, this, context = context, scope = scope)

fun <T> Flow<T>.toUninitializedObservable(
    coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate
) = FlowObservable(coroutineScope, context, this)

fun <T> MutableStateFlow<T>.toInitializedObservable(
    coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate
) = FlowInitializedObservable(this.value, coroutineScope, context, this)

fun <T> Flow<T>.toInitializedObservable(
    initialValue: T,
    coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate
) = FlowInitializedObservable(initialValue, coroutineScope, context, this)

fun <R : T, T> Flow<T?>.toDefaultObservable(
    defaultValue: R,
    initialValue: T? = defaultValue,
    coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate
) = DefaultFlowObservable(defaultValue, initialValue, coroutineScope, context, this)

fun <R : T?, T> HotFlowable<T?>.toDefaultSubject(
    defaultValue: R,
    coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate
) = HotFlowableDefaultSubject(defaultValue, coroutineScope, context, this)

fun <T> HotFlowable<T>.toInitializedSubject(
    coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate
) = HotFlowableInitializedSubject(coroutineScope, context, this)

fun <R : T, T> MutableStateFlow<T?>.toDefaultSubject(
    defaultValue: R,
    coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate
) = StateFlowDefaultSubject(defaultValue, coroutineScope, context, this)

fun <T> MutableStateFlow<T>.toInitializedSubject(
    coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate
) = StateFlowInitializedSubject(coroutineScope, context, this)

fun <T> MutableSharedFlow<T>.toUninitializedSubject(
    coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate
) = SharedFlowSubject(
    coroutineScope,
    context,
    this
)

fun <T> MutableSharedFlow<T>.toInitializedSubject(
    initialValue: T,
    coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate
) = SharedFlowInitializedSubject(
    initialValue,
    coroutineScope,
    context,
    this
)

fun <R : T?, T> MutableSharedFlow<T?>.toDefaultSubject(
    defaultValue: R,
    initialValue: T? = defaultValue,
    coroutineScope: CoroutineScope,
    context: CoroutineContext = Dispatchers.Main.immediate
) = SharedFlowDefaultSubject(
    defaultValue,
    initialValue,
    coroutineScope,
    context,
    this
)

fun <T> observableOf(initialValue: T) = SimpleInitializedObservable(initialValue)

fun <T> subjectOf(initialValue: T) = SimpleInitializedSubject(initialValue)

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.viewmodel

import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.Navigator
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren

/**
 * Simple ViewModel class
 */
expect open class ViewModel internal constructor(allowFreezing: Boolean = false) {
    /**
     * [CoroutineScope] of the ViewModel
     */
    val coroutineScope: CoroutineScope

    /**
     * Called when the ViewModel is cleared
     */
    protected open fun onCleared()
}

/**
 * Default [ViewModel] implementation respecting the Lifecycle of the presenting view
 */
open class BaseViewModel(allowFreezing: Boolean = false) : ViewModel(allowFreezing) {

    private val resumedJobs = SupervisorJob()

    /**
     * Function to be called when the presenting views lifecycle begins
     */
    fun didResume() {
        onResume(CoroutineScope(coroutineScope.coroutineContext + resumedJobs))
    }

    /**
     * Custom handler when the presenting views lifecycle begins
     * @param scope A [CoroutineScope] that lives during the lifecycle of the presenting view
     */
    protected open fun onResume(scope: CoroutineScope) {}

    /**
     * Function to be called when the presenting views lifecycle ends
     */
    fun didPause() {
        onPause()
        resumedJobs.cancelChildren()
    }

    /**
     * Custom handler when the presenting views lifecycle ends
     */
    protected open fun onPause() {}
}

/**
 * Default [ViewModel] allowing navigation
 * @param navigator The [Navigator] handling navigation
 */
open class NavigatingViewModel<A : NavigationAction<*>>(val navigator: Navigator<A>, allowFreezing: Boolean = false) : BaseViewModel(allowFreezing)

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.navigation

import platform.CoreFoundation.CFStringRef
import platform.CoreServices.kUTTypeImage
import platform.CoreServices.kUTTypeMovie
import platform.Foundation.NSData
import platform.Foundation.NSURL
import platform.MessageUI.MFMailComposeViewController
import platform.MessageUI.MFMailComposeViewControllerDelegateProtocol
import platform.MessageUI.MFMessageComposeViewController
import platform.MessageUI.MFMessageComposeViewControllerDelegateProtocol
import platform.Messages.MSMessage
import platform.UIKit.NSLayoutConstraint
import platform.UIKit.UIBarButtonItem
import platform.UIKit.UIDocumentBrowserAction
import platform.UIKit.UIDocumentBrowserUserInterfaceStyle
import platform.UIKit.UIDocumentBrowserUserInterfaceStyleLight
import platform.UIKit.UIDocumentBrowserViewController
import platform.UIKit.UIDocumentBrowserViewControllerDelegateProtocol
import platform.UIKit.UIImage
import platform.UIKit.UIImagePickerController
import platform.UIKit.UIImagePickerControllerDelegateProtocol
import platform.UIKit.UIImagePickerControllerSourceType
import platform.UIKit.UIInterfaceOrientation
import platform.UIKit.UIInterfaceOrientationMask
import platform.UIKit.UIModalPresentationAutomatic
import platform.UIKit.UIModalPresentationStyle
import platform.UIKit.UIModalTransitionStyle
import platform.UIKit.UIModalTransitionStyleCoverVertical
import platform.UIKit.UINavigationController
import platform.UIKit.UINavigationControllerDelegateProtocol
import platform.UIKit.UINavigationControllerOperation
import platform.UIKit.UIView
import platform.UIKit.UIViewController
import platform.UIKit.UIViewControllerAnimatedTransitioningProtocol
import platform.UIKit.UIViewControllerInteractiveTransitioningProtocol
import platform.darwin.NSObject

/**
 * Spec used by [Navigator] to determine how to perform Navigation
 */
sealed class NavigationSpec {

    /**
     * Navigates to a new view controller by pushing it on top of the parents [UINavigationController]
     * @param animated Specifies whether transition should be animated
     * @param push Function to create the [UIViewController] to push
     */
    data class Push(val animated: Boolean = true, val push: () -> UIViewController) : NavigationSpec()

    /**
     * Pops the viewController on the parents [UINavigationController].
     * @param to Optional [UIViewController] to pop to. If not provided the viewcontroller on top is popped.
     * @param animated Specifies whether transition should be animated
     */
    data class Pop(val to: UIViewController? = null, val animated: Boolean = true) : NavigationSpec()

    /**
     * Lets the parent present a [UIViewController] using [UIViewController.presentViewController].
     * @param animated Specifies whether transition should be animated
     * @param presentationStyle The [UIModalPresentationStyle] to present the viewcontroller in. Defaults to [UIModalPresentationAutomatic]
     * @param transitionStyle The [UIModalTransitionStyle] to transition to. Defaults to [UIModalTransitionStyleCoverVertical]
     * @param present Function to create the [UIViewController] to present
     * @param completion Optional function to call when presentation has completed
     */
    data class Present(
        val animated: Boolean = true,
        val presentationStyle: UIModalPresentationStyle = UIModalPresentationAutomatic,
        val transitionStyle: UIModalTransitionStyle = UIModalTransitionStyleCoverVertical,
        val present: () -> UIViewController,
        val completion: (() -> Unit)? = null
    ) : NavigationSpec()

    /**
     * Dismisses the parent.
     * @param toDismiss Determines the [UIViewController] to dismiss based on the provided parent viewController.
     * @param animated Specifies whether transition should be animated
     * @param completion Optional function to call when dismissal has completed
     */
    data class Dismiss(val toDismiss: (UIViewController) -> UIViewController = { it }, val animated: Boolean = false, val completion: (() -> Unit)? = null) : NavigationSpec()

    /**
     * Lets the parent show a [UIViewController] using [UIViewController.showViewController]
     * @param detail When set too true, shows using [UIViewController.showDetailViewController]
     * @param show Function to create the [UIViewController] to show
     */
    data class Show(val detail: Boolean = false, val show: () -> UIViewController) : NavigationSpec()

    /**
     * Triggers a segue with a given identifier on the parent
     * @param identifier The identifier of the segue to perform
     */
    data class Segue(val identifier: String) : NavigationSpec()

    /**
     * Adds a nested [UIViewController] to a container [UIView].
     * @param type The [Type] of presentation
     * @param containerView The [UIView] to which the [UIViewController] should be added. Must be a child of the parent
     * @param nested Function to create the [UIViewController] to add to the container view
     * @param constraints Optional function that takes the child and container view and returns the constraints to set. If left empty, the child will default to match the container view size
     */
    data class Nested(
        val type: Type = Type.Add,
        val containerView: UIView,
        val nested: () -> UIViewController,
        val constraints: ((UIView, UIView) -> List<NSLayoutConstraint>)? = null
    ) : NavigationSpec() {

        /**
         * Nested Presentation type
         */
        sealed class Type {
            /**
             * Adds the [UIViewController] to the container without removing any other views from the container
             */
            object Add : Type()

            /**
             * Replaces the current content matching a tag from the container before adding the new view
             * @param tag Tag added to the [UIView] of the [UIViewController] to determine whether it should be replaced
             */
            data class Replace(val tag: Long) : Type()
        }
    }

    /**
     * Presents a [UIImagePickerController] to the parent
     * @param mediaType Set of allowed [MediaType]s to pick
     * @param navigationDelegate The [UINavigationControllerDelegateProtocol] added to the [UIImagePickerController]
     * @param imagePickerDelegate The [UIImagePickerControllerDelegateProtocol] added to the [UIImagePickerController]
     * @param animated Specifies whether transition should be animated
     * @param completion Optional function called when presentation is completed
     */
    data class ImagePicker(
        val sourceType: UIImagePickerControllerSourceType = UIImagePickerControllerSourceType.UIImagePickerControllerSourceTypePhotoLibrary,
        val mediaType: Set<MediaType> = setOf(MediaType.Image),
        val navigationDelegate: UINavigationControllerDelegateProtocol,
        val imagePickerDelegate: UIImagePickerControllerDelegateProtocol,
        val animated: Boolean = false,
        val completion: (() -> Unit)? = null
    ) : NavigationSpec() {

        @Suppress("CONFLICTING_OVERLOADS")
        internal val delegate: UINavigationControllerDelegateProtocol = object : NSObject(), UIImagePickerControllerDelegateProtocol, UINavigationControllerDelegateProtocol {

            override fun navigationController(navigationController: UINavigationController, willShowViewController: UIViewController, animated: Boolean) {
                navigationDelegate.navigationController(navigationController, willShowViewController = willShowViewController, animated = animated)
            }

            override fun navigationController(navigationController: UINavigationController, didShowViewController: UIViewController, animated: Boolean) {
                navigationDelegate.navigationController(navigationController, didShowViewController = didShowViewController, animated = animated)
            }

            override fun navigationController(
                navigationController: UINavigationController,
                animationControllerForOperation: UINavigationControllerOperation,
                fromViewController: UIViewController,
                toViewController: UIViewController
            ): UIViewControllerAnimatedTransitioningProtocol? {
                return navigationDelegate.navigationController(navigationController, animationControllerForOperation, fromViewController, toViewController)
            }

            override fun navigationController(
                navigationController: UINavigationController,
                interactionControllerForAnimationController: UIViewControllerAnimatedTransitioningProtocol
            ): UIViewControllerInteractiveTransitioningProtocol? {
                return navigationDelegate.navigationController(navigationController, interactionControllerForAnimationController)
            }

            override fun navigationControllerSupportedInterfaceOrientations(navigationController: UINavigationController): UIInterfaceOrientationMask {
                return navigationDelegate.navigationControllerSupportedInterfaceOrientations(navigationController)
            }

            override fun navigationControllerPreferredInterfaceOrientationForPresentation(navigationController: UINavigationController): UIInterfaceOrientation {
                return navigationDelegate.navigationControllerPreferredInterfaceOrientationForPresentation(navigationController)
            }

            override fun imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo: Map<Any?, *>) {
                imagePickerDelegate.imagePickerController(picker, didFinishPickingMediaWithInfo)
            }

            override fun imagePickerController(picker: UIImagePickerController, didFinishPickingImage: UIImage, editingInfo: Map<Any?, *>?) {
                imagePickerDelegate.imagePickerController(picker, didFinishPickingImage, editingInfo)
            }

            override fun imagePickerControllerDidCancel(picker: UIImagePickerController) {
                imagePickerDelegate.imagePickerControllerDidCancel(picker)
            }
        }

        /**
         * The type of media that can be picked
         */
        sealed class MediaType {

            abstract val typeString: CFStringRef?

            object Image : MediaType() { override val typeString = kUTTypeImage }
            object Movie : MediaType() { override val typeString = kUTTypeMovie }
        }
    }

    /**
     * Presents a [MFMailComposeViewController]
     * @param emailSettings [EmailSettings] for composing the email
     * @param delegate Optional [MFMailComposeViewControllerDelegateProtocol] added to the [MFMailComposeViewController]
     * @param animated Specifies whether the transition should be animated
     * @param completion Optional function called when presentation is completed
     */
    data class Email(
        val emailSettings: EmailSettings,
        val delegate: MFMailComposeViewControllerDelegateProtocol? = null,
        val animated: Boolean = false,
        val completion: (() -> Unit)? = null
    ) : NavigationSpec() {

        /**
         * The type of formatting used for composing the email
         */
        sealed class Type {
            object Plain : Type()
            object Stylized : Type()
        }

        /**
         * An attachement added to the email
         * @param data [NSData] to add to the email
         * @param mimeType Mime type of the file
         * @param fileName File name of the file
         */
        data class Attachment(val data: NSData, val mimeType: String, val fileName: String)

        /**
         * Settings for composing the email
         * @param type [Type] used for formatting
         * @param to List of emails to send the email to
         * @param cc List of emails to cc the email to
         * @param bcc List of emails to bcc the emails to
         * @param subject Optional subject of the email
         * @param body Optional body of the email
         * @param attachments List of [Attachment] to add to the email
         */
        data class EmailSettings(
            val type: Type = Type.Plain,
            val to: List<String> = emptyList(),
            val cc: List<String> = emptyList(),
            val bcc: List<String> = emptyList(),
            val subject: String? = null,
            val body: String? = null,
            val attachments: List<Attachment> = emptyList()
        )
    }

    /**
     * Presents a [UIDocumentBrowserViewController]
     * @param documentSelectorSettings The [DocumentSelectorSettings] for selecting the document
     * @param documentSelectorAppearance The [DocumentSelectorAppearance] for configuring the appearance of the selector
     * @param delegate The [UIDocumentBrowserViewControllerDelegateProtocol] added to the [UIDocumentBrowserViewController]
     * @param animated Specifies whether transition is animated
     * @param completion Optional function called when presentation is completed
     */
    data class DocumentSelector(
        val documentSelectorSettings: DocumentSelectorSettings,
        val documentSelectorAppearance: DocumentSelectorAppearance,
        val delegate: UIDocumentBrowserViewControllerDelegateProtocol,
        val animated: Boolean = false,
        val completion: (() -> Unit)? = null
    ) : NavigationSpec() {

        /**
         * Configures the settings for a [UIDocumentBrowserViewController]
         * @param types List of file types that can be opened
         * @param allowMultiple Indicates whether the user can select multiple files
         * @param allowCreation Indicates whether the user can create new files
         */
        data class DocumentSelectorSettings(val types: Set<String>, val allowMultiple: Boolean = false, val allowCreation: Boolean = false)
        data class DocumentSelectorAppearance(
            val interfaceStyle: UIDocumentBrowserUserInterfaceStyle = UIDocumentBrowserUserInterfaceStyleLight,
            val leadingNavigationBarButtonItems: List<UIBarButtonItem> = emptyList(),
            val trailingNavigationBarButtonItems: List<UIBarButtonItem> = emptyList(),
            val customActions: List<UIDocumentBrowserAction> = emptyList(),
            val createTitle: String,
            val documentAspectRatio: Double = 2.0 / 3.0,
            val showFileExtensions: Boolean = false
        )
    }

    /**
     * Opens the phone dialer
     * @param phoneNumber The phone number to dial
     */
    data class Phone(val phoneNumber: String) : NavigationSpec()

    /**
     * Opens the Phone settings
     */
    object Settings : NavigationSpec()

    /**
     * Presents a [MFMessageComposeViewController]
     * @param messageSettings The [MessageSettings] for configuring the message
     * @param delegate The [MFMessageComposeViewControllerDelegateProtocol] to add to the [MFMessageComposeViewController]
     * @param animated Specifies whether transition is animated
     * @param completion Optional function called when presentation is completed
     */
    data class Message(
        val messageSettings: MessageSettings,
        val delegate: MFMessageComposeViewControllerDelegateProtocol,
        val animated: Boolean = false,
        val completion: (() -> Unit)? = null
    ) : NavigationSpec() {

        /**
         * An attachement added to the message
         * @param data [NSData] to add to the email
         * @param mimeType Mime type of the file
         * @param fileName File name of the file
         */
        data class Attachment(val data: NSData, val mimeType: String, val fileName: String)

        /**
         * Settings for configuring the message
         * @param recipients list of phone numbers to send the message to
         * @param subject Optional subject of the message
         * @param body Optional body of the message
         * @param disableAttachments Disables the user from adding attachements
         * @param attachments The list of [Attachment] added to the message
         */
        data class MessageSettings(
            val recipients: List<String> = emptyList(),
            val subject: String? = null,
            val body: String? = null,
            val message: MSMessage? = null,
            val disableAttachments: Boolean = false,
            val attachments: List<Attachment> = emptyList()
        )
    }

    /**
     * Opens the browser
     * @param url The [NSURL] to open in the browser
     */
    data class Browser(val url: NSURL, val viewType: Type) : NavigationSpec() {
        sealed class Type {
            data class SafariView(val animated: Boolean, val completion: (() -> Unit)? = null) : Type()
            object Normal : Type()
        }
    }

    /**
     * Opens the AppStore for a given productId
     * @param openMode The [OpenMode] used for opening the third party app
     */
    data class ThirdParty(val openMode: OpenMode) : NavigationSpec() {
        sealed class OpenMode {
            /**
             * Tries to open the app or navigates to the store if it does not exist
             * @param urlScheme The [NSURL] scheme for opening the app
             * @param storeInfo The [StoreInfo] used for opening the app store
             */
            data class FallbackToAppStore(val urlScheme: NSURL, val storeInfo: StoreInfo) : OpenMode()

            /**
             * Only navigates whn the app is installed
             * @param urlScheme The [NSURL] scheme for opening the app
             */
            data class OnlyWhenInstalled(val url: NSURL) : OpenMode()

            /**
             * Opens the AppStore
             * @param storeInfo The [StoreInfo] used for opening the app store
             */
            data class AppStore(val storeInfo: StoreInfo) : OpenMode()
        }

        /**
         * Information to specify how the appstore should be opened
         */
        data class StoreInfo(
            /**
             * Identifier of the AppStore page
             */
            val appId: Int,
            /**
             * Identifier of the product to promote at the top of the app store
             */
            val productId: String? = null,
            /**
             *  The token of the advertising partner you wish to use for any purchase made in the app store
             */
            val advertisingPartnerToken: String? = null,
            /**
             *  The token of the affiliate you wish to use for any purchase made in the app store
             */
            val affiliateToken: String? = null,
            /**
             * The token for the App Analytics campaign
             */
            val campaignToken: String? = null,
            /**
             * The provider token for the developer that created the app
             */
            val providerToken: String? = null
        )
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.navigation

import com.splendo.kaluga.base.GCScheduler
import kotlinx.cinterop.pointed
import platform.CoreGraphics.CGFloat
import platform.Foundation.NSNumber
import platform.Foundation.NSURL
import platform.Foundation.numberWithInt
import platform.MessageUI.MFMailComposeViewController
import platform.MessageUI.MFMessageComposeViewController
import platform.SafariServices.SFSafariViewController
import platform.StoreKit.SKStoreProductParameterAdvertisingPartnerToken
import platform.StoreKit.SKStoreProductParameterAffiliateToken
import platform.StoreKit.SKStoreProductParameterCampaignToken
import platform.StoreKit.SKStoreProductParameterITunesItemIdentifier
import platform.StoreKit.SKStoreProductParameterProductIdentifier
import platform.StoreKit.SKStoreProductParameterProviderToken
import platform.StoreKit.SKStoreProductViewController
import platform.StoreKit.SKStoreProductViewControllerDelegateProtocol
import platform.UIKit.NSLayoutAttributeBottom
import platform.UIKit.NSLayoutAttributeLeading
import platform.UIKit.NSLayoutAttributeTop
import platform.UIKit.NSLayoutAttributeTrailing
import platform.UIKit.NSLayoutConstraint
import platform.UIKit.NSLayoutRelationEqual
import platform.UIKit.UIApplication
import platform.UIKit.UIApplicationOpenSettingsURLString
import platform.UIKit.UIDocumentBrowserViewController
import platform.UIKit.UIImagePickerController
import platform.UIKit.UIViewController
import platform.UIKit.addChildViewController
import platform.UIKit.addConstraint
import platform.UIKit.addSubview
import platform.UIKit.childViewControllers
import platform.UIKit.didMoveToParentViewController
import platform.UIKit.navigationController
import platform.UIKit.removeFromParentViewController
import platform.UIKit.removeFromSuperview
import platform.UIKit.translatesAutoresizingMaskIntoConstraints
import platform.UIKit.willMoveToParentViewController
import platform.darwin.NSInteger
import platform.darwin.NSObject
import kotlin.native.ref.WeakReference

actual interface Navigator<A : NavigationAction<*>> {
    actual fun navigate(action: A)
}

/**
 * Implementation of [Navigator] used for navigating to and from [UIViewController]. Takes a mapper function to map all [NavigationAction] to a [NavigationSpec]
 * Whenever [navigate] is called, this class maps it to a [NavigationSpec] and performs navigation according to that
 * @param parent The [UIViewController] managing the navigation
 * @param navigationMapper A function mapping the [NavigationAction] to [NavigationSpec]
 */
class ViewControllerNavigator<A : NavigationAction<*>>(
    parentVC: UIViewController,
    private val navigationMapper: (A) -> NavigationSpec
) : Navigator<A> {

    private inner class StoreKitDelegate : NSObject(), SKStoreProductViewControllerDelegateProtocol {

        override fun productViewControllerDidFinish(viewController: SKStoreProductViewController) {
            viewController.dismissViewControllerAnimated(true, null)
        }
    }

    private val parent = WeakReference(parentVC)
    private val storeKitDelegate: StoreKitDelegate by lazy { StoreKitDelegate() }

    override fun navigate(action: A) {
        navigate(navigationMapper.invoke(action), action.bundle)
    }

    private fun navigate(spec: NavigationSpec, bundle: NavigationBundle<*>?) {
        when (spec) {
            is NavigationSpec.Push -> pushViewController(spec)
            is NavigationSpec.Pop -> popViewController(spec)
            is NavigationSpec.Present -> presentViewController(spec)
            is NavigationSpec.Dismiss -> dismissViewController(spec)
            is NavigationSpec.Show -> showViewController(spec)
            is NavigationSpec.Segue -> segueToViewController(spec, bundle)
            is NavigationSpec.Nested -> embedNestedViewController(spec)
            is NavigationSpec.ImagePicker -> presentImagePicker(spec)
            is NavigationSpec.Email -> presentMailComposer(spec)
            is NavigationSpec.DocumentSelector -> presentDocumentBrowser(spec)
            is NavigationSpec.Message -> presentMessageComposer(spec)
            is NavigationSpec.Phone -> openDialer(spec)
            is NavigationSpec.Settings -> openSettings()
            is NavigationSpec.Browser -> openBrowser(spec)
            is NavigationSpec.ThirdParty -> openThirdPartyApp(spec)
        }
        // Since navigation often references UIViewControllers they should be freed up to keep ARC working
        GCScheduler.schedule()
    }

    private fun pushViewController(pushSpec: NavigationSpec.Push) {
        val parent = assertParent() ?: return
        assert(parent.navigationController != null)
        parent.navigationController?.pushViewController(pushSpec.push(), pushSpec.animated)
    }

    private fun popViewController(popSpec: NavigationSpec.Pop) {
        val parent = assertParent() ?: return
        assert(parent.navigationController != null)
        popSpec.to?.let {
            parent.navigationController?.popToViewController(it, popSpec.animated)
        } ?: parent.navigationController?.popViewControllerAnimated(popSpec.animated)
    }

    private fun presentViewController(presentSpec: NavigationSpec.Present) {
        val toPresent = presentSpec.present()
        toPresent.modalPresentationStyle = presentSpec.presentationStyle
        toPresent.modalTransitionStyle = presentSpec.transitionStyle
        assertParent()?.presentViewController(toPresent, presentSpec.animated) { presentSpec.completion?.invoke() }
    }

    private fun dismissViewController(dismissSpec: NavigationSpec.Dismiss) {
        val parent = assertParent() ?: return
        val toDismiss = dismissSpec.toDismiss(parent)
        toDismiss.dismissViewControllerAnimated(dismissSpec.animated) { dismissSpec.completion?.invoke() }
    }

    private fun showViewController(showSpec: NavigationSpec.Show) {
        val toShow = showSpec.show()
        val parent = assertParent() ?: return
        if (showSpec.detail) {
            parent.showDetailViewController(toShow, parent)
        } else {
            parent.showViewController(toShow, parent)
        }
    }

    private fun segueToViewController(segueSpec: NavigationSpec.Segue, bundle: NavigationBundle<*>?) {
        assertParent()?.performSegueWithIdentifier(segueSpec.identifier, bundle)
    }

    private fun embedNestedViewController(nestedSpec: NavigationSpec.Nested) {
        val parent = assertParent() ?: return
        when (val type = nestedSpec.type) {
            is NavigationSpec.Nested.Type.Replace -> {
                parent.childViewControllers.map { it as UIViewController }.filter { it.view.tag.toLong() == type.tag }.forEach {
                    it.willMoveToParentViewController(null)
                    it.view.removeFromSuperview()
                    it.removeFromParentViewController()
                }
            }
        }

        val child = nestedSpec.nested()
        when (val type = nestedSpec.type) {
            is NavigationSpec.Nested.Type.Replace -> {
                child.view.tag = type.tag as NSInteger
            }
        }
        child.view.translatesAutoresizingMaskIntoConstraints = false
        parent.addChildViewController(child)
        nestedSpec.containerView.addSubview(child.view)
        val constraints = nestedSpec.constraints?.invoke(child.view, nestedSpec.containerView) ?: listOf(
            NSLayoutAttributeLeading,
            NSLayoutAttributeTrailing,
            NSLayoutAttributeTop,
            NSLayoutAttributeBottom
        ).map { attribute ->
            CGFloat
            NSLayoutConstraint.constraintWithItem(child.view, attribute, NSLayoutRelationEqual, nestedSpec.containerView, attribute, 1.0 as CGFloat, 0.0 as CGFloat)
        }
        constraints.forEach { nestedSpec.containerView.addConstraint(it) }
        child.didMoveToParentViewController(parent)
    }

    private fun presentImagePicker(imagePickerSpec: NavigationSpec.ImagePicker) {
        val isSourceTypeAvailable = UIImagePickerController.isSourceTypeAvailable(imagePickerSpec.sourceType)
        assert(isSourceTypeAvailable)
        if (!isSourceTypeAvailable) return
        val mediaTypes = imagePickerSpec.mediaType.map { it.typeString?.pointed.toString() }
        val isMediaTypesAvailable = UIImagePickerController.availableMediaTypesForSourceType(imagePickerSpec.sourceType)?.containsAll(mediaTypes) == true
        assert(isMediaTypesAvailable)
        if (!isMediaTypesAvailable) return
        val pickerVC = UIImagePickerController()
        pickerVC.sourceType = imagePickerSpec.sourceType
        pickerVC.mediaTypes = mediaTypes
        pickerVC.delegate = imagePickerSpec.delegate
        assertParent()?.presentViewController(pickerVC, imagePickerSpec.animated) { imagePickerSpec.completion?.invoke() }
    }

    private fun presentMailComposer(mailSpec: NavigationSpec.Email) {
        val canSendMail = MFMailComposeViewController.canSendMail()
        assert(canSendMail)
        if (!canSendMail) return
        val composeVC = MFMailComposeViewController()
        composeVC.mailComposeDelegate = mailSpec.delegate

        val settings = mailSpec.emailSettings
        composeVC.setToRecipients(settings.to)
        composeVC.setCcRecipients(settings.cc)
        composeVC.setBccRecipients(settings.bcc)
        settings.subject?.let {
            composeVC.setSubject(it)
        }
        settings.body?.let {
            composeVC.setMessageBody(it, settings.type is NavigationSpec.Email.Type.Stylized)
        }

        settings.attachments.forEach {
            composeVC.addAttachmentData(it.data, it.mimeType, it.fileName)
        }

        assertParent()?.presentViewController(composeVC, mailSpec.animated) { mailSpec.completion?.invoke() }
    }

    private fun presentDocumentBrowser(browserSpec: NavigationSpec.DocumentSelector) {
        val settings = browserSpec.documentSelectorSettings
        val browserVc = UIDocumentBrowserViewController(forOpeningFilesWithContentTypes = settings.types.toList())
        browserVc.allowsDocumentCreation = settings.allowCreation
        browserVc.allowsPickingMultipleItems = settings.allowMultiple

        val appearance = browserSpec.documentSelectorAppearance
        browserVc.browserUserInterfaceStyle = appearance.interfaceStyle
        browserVc.additionalLeadingNavigationBarButtonItems = appearance.leadingNavigationBarButtonItems
        browserVc.additionalTrailingNavigationBarButtonItems = appearance.trailingNavigationBarButtonItems
        browserVc.shouldShowFileExtensions = appearance.showFileExtensions
        browserVc.customActions = appearance.customActions
        browserVc.defaultDocumentAspectRatio = appearance.documentAspectRatio as CGFloat
        browserVc.localizedCreateDocumentActionTitle = appearance.createTitle

        browserVc.delegate = browserSpec.delegate

        assertParent()?.presentViewController(browserVc, browserSpec.animated) { browserSpec.completion?.invoke() }
    }

    private fun presentMessageComposer(messageSpec: NavigationSpec.Message) {
        val canSendText = MFMessageComposeViewController.canSendText()
        assert(canSendText)
        if (!canSendText) return
        val composeVC = MFMessageComposeViewController()
        composeVC.messageComposeDelegate = messageSpec.delegate

        val settings = messageSpec.messageSettings
        composeVC.setRecipients(settings.recipients)
        if (MFMessageComposeViewController.canSendSubject()) {
            settings.subject?.let {
                composeVC.setSubject(it)
            }
        }
        settings.body?.let { composeVC.setBody(it) }
        settings.message?.let { composeVC.setMessage(it) }

        if (MFMessageComposeViewController.canSendAttachments()) {
            settings.attachments.forEach {
                composeVC.addAttachmentData(it.data, it.mimeType, it.fileName)
            }
            if (settings.disableAttachments) {
                composeVC.disableUserAttachments()
            }
        }

        assertParent()?.presentViewController(composeVC, messageSpec.animated) { messageSpec.completion?.invoke() }
    }

    private fun openDialer(phoneSpec: NavigationSpec.Phone) {
        openUrl("tel://${phoneSpec.phoneNumber}")
    }

    private fun openSettings() {
        openUrl(UIApplicationOpenSettingsURLString)
    }

    private fun openBrowser(browserSpec: NavigationSpec.Browser) {
        when (val spec = browserSpec.viewType) {
            NavigationSpec.Browser.Type.Normal -> {
                UIApplication.sharedApplication.openURL(browserSpec.url)
            }
            is NavigationSpec.Browser.Type.SafariView -> {
                val safariVc = SFSafariViewController(browserSpec.url)
                assertParent()?.presentViewController(safariVc, spec.animated) {
                    spec.completion?.invoke()
                }
            }
        }
    }

    private fun openUrl(urlString: String) {
        NSURL.URLWithString(urlString)?.let {
            UIApplication.sharedApplication.openURL(it)
        }
    }

    private fun openThirdPartyApp(thirdPartySpec: NavigationSpec.ThirdParty) {
        when (val openMode = thirdPartySpec.openMode) {
            is NavigationSpec.ThirdParty.OpenMode.AppStore -> openAppStore(openMode.storeInfo)
            is NavigationSpec.ThirdParty.OpenMode.FallbackToAppStore -> openThirdPartyApp(openMode.urlScheme, openMode.storeInfo)
            is NavigationSpec.ThirdParty.OpenMode.OnlyWhenInstalled -> openThirdPartyApp(openMode.url, null)
        }
    }

    private fun openThirdPartyApp(urlScheme: NSURL, storeInfo: NavigationSpec.ThirdParty.StoreInfo?) {
        if (UIApplication.sharedApplication.canOpenURL(urlScheme)) {
            UIApplication.sharedApplication.openURL(urlScheme)
        } else {
            storeInfo?.let {
                openAppStore(storeInfo)
            }
        }
    }

    private fun openAppStore(storeInfo: NavigationSpec.ThirdParty.StoreInfo) {
        val parent = assertParent() ?: return
        val parameters = mutableMapOf<Any?, Any>(SKStoreProductParameterITunesItemIdentifier to NSNumber.numberWithInt(storeInfo.appId))
        storeInfo.productId?.let {
            parameters[SKStoreProductParameterProductIdentifier] = it
        }
        storeInfo.advertisingPartnerToken?.let {
            parameters[SKStoreProductParameterAdvertisingPartnerToken] = it
        }
        storeInfo.affiliateToken?.let {
            parameters[SKStoreProductParameterAffiliateToken] = it
        }
        storeInfo.campaignToken?.let {
            parameters[SKStoreProductParameterCampaignToken] = it
        }
        storeInfo.providerToken?.let {
            parameters[SKStoreProductParameterProviderToken] = it
        }
        val productViewController = SKStoreProductViewController().apply {
            delegate = storeKitDelegate
        }
        productViewController.loadProductWithParameters(
            parameters
        ) { isLoaded, _ ->
            if (isLoaded) {
                parent.presentViewController(productViewController, true, null)
            }
        }
    }

    private fun assertParent() = parent.get().also { assert(it != null) }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.observable

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
import kotlin.coroutines.CoroutineContext
import kotlin.properties.ReadOnlyProperty

actual interface WithState<T> {
    actual val stateFlow: StateFlow<T>
    actual val valueDelegate: ReadOnlyProperty<Any?, T>
}

actual abstract class BaseSubject<R : T, T, OO : ObservableOptional<R>> actual constructor(
    observation: Observation<R, T, OO>,
    stateFlowToBind: () -> StateFlow<R?>
) : AbstractBaseSubject<R, T, OO>(observation, stateFlowToBind) {

    final override fun bind(coroutineScope: CoroutineScope, context: CoroutineContext) {
        super.bind(coroutineScope, context)
    }
}

actual abstract class BaseUninitializedSubject<T> actual constructor(
    observation: ObservationUninitialized<T>
) : AbstractBaseUninitializedSubject<T>(observation)

actual abstract class BaseInitializedSubject<T> actual constructor(observation: ObservationInitialized<T>) : AbstractBaseInitializedSubject<T>(observation) {

    actual constructor(
        initialValue: ObservableOptional.Value<T>
    ) : this (ObservationInitialized(initialValue))
}

actual abstract class BaseDefaultSubject<R : T?, T> actual constructor(
    observation: ObservationDefault<R, T?>
) : AbstractBaseDefaultSubject<R, T>(observation) {

    actual constructor(
        defaultValue: ObservableOptional.Value<R>,
        initialValue: ObservableOptional.Value<T?>
    ) : this(observation = ObservationDefault<R, T?>(defaultValue, initialValue))
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.observable

import com.splendo.kaluga.base.GCScheduler

actual class SimpleDisposable actual constructor(onDispose: DisposeHandler) : BaseSimpleDisposable(onDispose) {

    override fun afterDispose() {
        GCScheduler.schedule()
    }
}

actual fun <R : T, T, OO : ObservableOptional<R>> addObserver(observation: Observation<R, T, OO>, observer: (R) -> Unit) {
    val observers = observersForObservation.getOrPut(observation) { mutableListOf() }
    observers.add(observer as (Any?) -> Unit)
}

actual fun <R : T, T, OO : ObservableOptional<R>> removeObserver(observation: Observation<R, T, OO>, observer: (R) -> Unit) {
    val observers = observersForObservation[observation] ?: return
    observers.remove(observer)
    if (observers.isEmpty())
        observersForObservation.remove(observation)
}

actual fun <R : T, T, OO : ObservableOptional<R>> observers(observation: Observation<R, T, OO>): List<(R) -> Unit> {
    return observersForObservation[observation] as? List<(R) -> Unit> ?: emptyList()
}

// Use this to have a thread local reference on iOS that does not get frozen
// TODO: this can be further improved by using a WeakRef
@ThreadLocal
private val observersForObservation = mutableMapOf<Observation<*, *, *>, MutableList<(Any?)->Unit>>()

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.viewmodel

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren
import kotlin.native.concurrent.ensureNeverFrozen

actual open class ViewModel internal actual constructor(val allowFreezing: Boolean) {

    init {
        if (!allowFreezing)
            ensureNeverFrozen()
    }

    private val lifecycleJob = SupervisorJob()

    actual val coroutineScope = CoroutineScope(Dispatchers.Main + lifecycleJob)

    /**
     * Call this when the [ViewModel] should be cleared to cancel all its coroutines.
     */
    fun clear() {
        onCleared()
    }

    protected actual open fun onCleared() {
        lifecycleJob.cancelChildren()
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.viewmodel

import com.splendo.kaluga.architecture.observable.Disposable
import com.splendo.kaluga.architecture.observable.DisposeBag
import platform.UIKit.UIViewController
import platform.UIKit.addChildViewController
import platform.UIKit.addSubview
import platform.UIKit.didMoveToParentViewController
import platform.UIKit.removeFromParentViewController
import platform.UIKit.removeFromSuperview
import platform.UIKit.willMoveToParentViewController

/**
 * Holds the lifecycle of a [BaseViewModel].
 * A single lifecycle starts at [UIViewController.viewDidAppear] and ends at [UIViewController.viewDidDisappear] of the bound [UIViewController]
 * This convenience class is the result of [BaseViewModel.addLifecycleManager].
 * Invoke [unbind] to unbind the Lifecycle from its bound [UIViewController]
 */
class LifecycleManager internal constructor(allowFreezing: Boolean, clearViewModel: () -> Unit) {

    internal val disposeBag: DisposeBag = DisposeBag(allowFreezing)
    private var onUnbind: (() -> Unit)? = clearViewModel

    /**
     * Unbinds the [BaseViewModel]
     */
    fun unbind() {
        onUnbind?.invoke()
        onUnbind = null
    }
}

/**
 * Callback invoked at the start of a lifecycle (corresponding to [UIViewController.viewDidAppear] of the bound ViewController.
 * Returns a List [Disposable] that are cleaned automatically at the end of each lifecycle.
 */
typealias onLifeCycleChanged = () -> List<Disposable>

internal class ViewModelLifecycleManager<VM : BaseViewModel>(private val viewModel: VM, private val onLifecycle: onLifeCycleChanged) : UIViewController(null, null) {

    internal val lifecycleManager = LifecycleManager(viewModel.allowFreezing) {
        viewModel.clear()
        willMoveToParentViewController(null)
        view.removeFromSuperview()
        removeFromParentViewController()
    }

    override fun viewDidAppear(animated: Boolean) {
        super.viewDidAppear(animated)

        viewModel.didResume()
        onLifecycle().forEach { lifecycleManager.disposeBag.add(it) }
    }

    override fun viewDidDisappear(animated: Boolean) {
        super.viewDidDisappear(animated)

        viewModel.didPause()
        lifecycleManager.disposeBag.dispose()
    }
}

/**
 * Adds a manager to automatically bind the [LifecycleManager] of a [BaseViewModel] to a [UIViewController].
 * This is achieved by adding an invisible child [UIViewController].
 * @param parent The containing [UIViewController]
 * @param onLifecycle This callback is invoked on each start of a new lifecycle.
 * @return The [LifecycleManager] bound to the [BaseViewModel]
 */
fun <VM : BaseViewModel> VM.addLifecycleManager(parent: UIViewController, onLifecycle: onLifeCycleChanged): LifecycleManager {
    val lifecycleManager = ViewModelLifecycleManager(this, onLifecycle)
    parent.addChildViewController(lifecycleManager)
    parent.view.addSubview(lifecycleManager.view)
    lifecycleManager.view.userInteractionEnabled = false
    lifecycleManager.didMoveToParentViewController(parent)
    return lifecycleManager.lifecycleManager
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.navigation

actual interface Navigator<A : NavigationAction<*>> {

    actual fun navigate(action: A)
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.observable

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
import kotlin.coroutines.CoroutineContext
import kotlin.properties.ReadOnlyProperty

actual interface WithState<T> {
    actual val stateFlow: StateFlow<T>
    actual val valueDelegate: ReadOnlyProperty<Any?, T>
}

actual abstract class BaseSubject<R : T, T, OO : ObservableOptional<R>> actual constructor(
    observation: Observation<R, T, OO>,
    stateFlowToBind: () -> StateFlow<R?>
) : AbstractBaseSubject<R, T, OO>(observation, stateFlowToBind) {

    final override fun bind(coroutineScope: CoroutineScope, context: CoroutineContext) {
        super.bind(coroutineScope, context)
    }
}

actual abstract class BaseUninitializedSubject<T> actual constructor(
    observation: ObservationUninitialized<T>
) : AbstractBaseUninitializedSubject<T>(observation)

actual abstract class BaseInitializedSubject<T> actual constructor(observation: ObservationInitialized<T>) : AbstractBaseInitializedSubject<T>(observation) {

    actual constructor(
        initialValue: ObservableOptional.Value<T>
    ) : this (ObservationInitialized(initialValue))
}

actual abstract class BaseDefaultSubject<R : T?, T> actual constructor(
    observation: ObservationDefault<R, T?>
) : AbstractBaseDefaultSubject<R, T>(observation) {

    actual constructor(
        defaultValue: ObservableOptional.Value<R>,
        initialValue: ObservableOptional.Value<T?>
    ) : this(observation = ObservationDefault<R, T?>(defaultValue, initialValue))
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.observable

actual class SimpleDisposable actual constructor(onDispose: DisposeHandler) : BaseSimpleDisposable(onDispose)

internal actual fun <R : T, T, OO : ObservableOptional<R>> addObserver(observation: Observation<R, T, OO>, observer: (R) -> Unit) {
    observation.observers.add(observer)
}

internal actual fun <R : T, T, OO : ObservableOptional<R>> removeObserver(observation: Observation<R, T, OO>, observer: (R) -> Unit) {
    observation.observers.remove(observer)
}

internal actual fun <R : T, T, OO : ObservableOptional<R>> observers(observation: Observation<R, T, OO>): List<(R) -> Unit> =
    observation.observers

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.viewmodel

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren

actual open class ViewModel internal actual constructor(allowFreezing: Boolean) {

    private val lifecycleJob = SupervisorJob()

    actual val coroutineScope = CoroutineScope(Dispatchers.Main + lifecycleJob)

    fun clear() {
        onCleared()
    }

    protected actual open fun onCleared() {
        lifecycleJob.cancelChildren()
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.navigation

actual interface Navigator<A : NavigationAction<*>> {

    actual fun navigate(action: A)
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.observable

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
import kotlin.coroutines.CoroutineContext
import kotlin.properties.ReadOnlyProperty

actual interface WithState<T> {
    actual val stateFlow: StateFlow<T>
    actual val valueDelegate: ReadOnlyProperty<Any?, T>
}

actual abstract class BaseSubject<R : T, T, OO : ObservableOptional<R>> actual constructor(
    observation: Observation<R, T, OO>,
    stateFlowToBind: () -> StateFlow<R?>
) : AbstractBaseSubject<R, T, OO>(observation, stateFlowToBind) {

    final override fun bind(coroutineScope: CoroutineScope, context: CoroutineContext) {
        super.bind(coroutineScope, context)
    }
}

actual abstract class BaseUninitializedSubject<T> actual constructor(
    observation: ObservationUninitialized<T>
) : AbstractBaseUninitializedSubject<T>(observation)

actual abstract class BaseInitializedSubject<T> actual constructor(observation: ObservationInitialized<T>) : AbstractBaseInitializedSubject<T>(observation) {

    actual constructor(
        initialValue: ObservableOptional.Value<T>
    ) : this (ObservationInitialized(initialValue))
}

actual abstract class BaseDefaultSubject<R : T?, T> actual constructor(
    observation: ObservationDefault<R, T?>
) : AbstractBaseDefaultSubject<R, T>(observation) {

    actual constructor(
        defaultValue: ObservableOptional.Value<R>,
        initialValue: ObservableOptional.Value<T?>
    ) : this(observation = ObservationDefault<R, T?>(defaultValue, initialValue))
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.observable

actual class SimpleDisposable actual constructor(onDispose: DisposeHandler) : BaseSimpleDisposable(onDispose)

internal actual fun <R : T, T, OO : ObservableOptional<R>> addObserver(observation: Observation<R, T, OO>, observer: (R) -> Unit) {
    observation.observers.add(observer)
}

internal actual fun <R : T, T, OO : ObservableOptional<R>> removeObserver(observation: Observation<R, T, OO>, observer: (R) -> Unit) {
    observation.observers.remove(observer)
}

internal actual fun <R : T, T, OO : ObservableOptional<R>> observers(observation: Observation<R, T, OO>): List<(R) -> Unit> =
    observation.observers

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.viewmodel

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren

actual open class ViewModel internal actual constructor(allowFreezing: Boolean) {

    private val lifecycleJob = SupervisorJob()

    actual val coroutineScope = CoroutineScope(Dispatchers.Main + lifecycleJob)

    fun clear() {
        onCleared()
    }

    protected actual open fun onCleared() {
        lifecycleJob.cancelChildren()
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.av

import com.splendo.kaluga.base.mainContinuation
import com.splendo.kaluga.logging.error
import com.splendo.kaluga.permissions.IOSPermissionsHelper
import com.splendo.kaluga.permissions.Permission
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionRefreshScheduler
import com.splendo.kaluga.permissions.PermissionState
import platform.AVFoundation.AVAuthorizationStatus
import platform.AVFoundation.AVAuthorizationStatusAuthorized
import platform.AVFoundation.AVAuthorizationStatusDenied
import platform.AVFoundation.AVAuthorizationStatusNotDetermined
import platform.AVFoundation.AVAuthorizationStatusRestricted
import platform.AVFoundation.AVCaptureDevice
import platform.AVFoundation.AVMediaType
import platform.AVFoundation.authorizationStatusForMediaType
import platform.AVFoundation.requestAccessForMediaType
import platform.Foundation.NSBundle

abstract class AVType<P : Permission> {

    abstract val permissionManager: PermissionManager<P>
    abstract val avMediaType: AVMediaType
    abstract val declarationName: String
}

class AVPermissionHelper<P : Permission>(private val bundle: NSBundle, private val type: AVType<P>) {

    private val authorizationStatus: suspend () -> IOSPermissionsHelper.AuthorizationStatus get() = suspend {
        AVCaptureDevice.authorizationStatusForMediaType(type.avMediaType).toAuthorizationStatus()
    }
    private val timerHelper = PermissionRefreshScheduler(type.permissionManager, authorizationStatus)

    fun requestPermission() {
        if (IOSPermissionsHelper.missingDeclarationsInPList(bundle, type.declarationName).isEmpty()) {
            timerHelper.isWaiting.value = true
            AVCaptureDevice.requestAccessForMediaType(
                type.avMediaType,
                mainContinuation { allowed ->
                    timerHelper.isWaiting.value = false
                    if (allowed) {
                        type.permissionManager.grantPermission()
                    } else {
                        type.permissionManager.revokePermission(true)
                    }
                }
            )
        } else {
            type.permissionManager.revokePermission(true)
        }
    }

    suspend fun initializeState(): PermissionState<P> {
        return when {
            AVCaptureDevice.devicesWithMediaType(type.avMediaType).isEmpty() -> PermissionState.Denied.Locked()
            else -> IOSPermissionsHelper.getPermissionState(authorizationStatus())
        }
    }

    suspend fun startMonitoring(interval: Long) {
        timerHelper.startMonitoring(interval)
    }

    suspend fun stopMonitoring() {
        timerHelper.stopMonitoring()
    }
}

private fun AVAuthorizationStatus.toAuthorizationStatus(): IOSPermissionsHelper.AuthorizationStatus {
    return when (this) {
        AVAuthorizationStatusAuthorized -> IOSPermissionsHelper.AuthorizationStatus.Authorized
        AVAuthorizationStatusDenied -> IOSPermissionsHelper.AuthorizationStatus.Denied
        AVAuthorizationStatusRestricted -> IOSPermissionsHelper.AuthorizationStatus.Restricted
        AVAuthorizationStatusNotDetermined -> IOSPermissionsHelper.AuthorizationStatus.NotDetermined
        else -> {
            error(
                "AVPermissionManager",
                "Unknown AVManagerAuthorization status={$this}"
            )
            IOSPermissionsHelper.AuthorizationStatus.NotDetermined
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.

 */

@file:JvmName("AndroidDateFormatter")
package com.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Date
import com.splendo.kaluga.base.utils.Locale
import com.splendo.kaluga.base.utils.TimeZone
import java.text.DateFormat
import java.text.DateFormatSymbols
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar

actual class DateFormatter private constructor(private val format: SimpleDateFormat) {

    actual companion object {

        actual fun dateFormat(
            style: DateFormatStyle,
            timeZone: TimeZone,
            locale: Locale
        ): DateFormatter = createDateFormatter(DateFormat.getDateInstance(style.javaStyle(), locale.locale) as SimpleDateFormat, timeZone)

        actual fun timeFormat(
            style: DateFormatStyle,
            timeZone: TimeZone,
            locale: Locale
        ): DateFormatter = createDateFormatter(DateFormat.getTimeInstance(style.javaStyle(), locale.locale) as SimpleDateFormat, timeZone)

        actual fun dateTimeFormat(
            dateStyle: DateFormatStyle,
            timeStyle: DateFormatStyle,
            timeZone: TimeZone,
            locale: Locale
        ): DateFormatter = createDateFormatter(DateFormat.getDateTimeInstance(dateStyle.javaStyle(), timeStyle.javaStyle(), locale.locale) as SimpleDateFormat, timeZone)

        actual fun patternFormat(pattern: String, timeZone: TimeZone, locale: Locale): DateFormatter = createDateFormatter(SimpleDateFormat(pattern, locale.locale), timeZone)

        private fun createDateFormatter(simpleDateFormat: SimpleDateFormat, timeZone: TimeZone): DateFormatter {
            return DateFormatter(simpleDateFormat).apply {
                this.timeZone = timeZone
            }
        }
    }

    private val symbols: DateFormatSymbols get() = format.dateFormatSymbols

    actual var pattern: String
        get() = format.toPattern()
        set(value) = format.applyPattern(value)

    actual var timeZone: TimeZone
        get() = TimeZone(format.timeZone)
        set(value) { format.timeZone = value.timeZone }

    actual var eras: List<String>
        get() = symbols.eras.toList()
        set(value) { updateSymbols { it.eras = value.toTypedArray() } }

    actual var months: List<String>
        get() = symbols.months.toList()
        set(value) { updateSymbols { it.months = value.toTypedArray() } }
    actual var shortMonths: List<String>
        get() = symbols.shortMonths.toList()
        set(value) { updateSymbols { it.shortMonths = value.toTypedArray() } }

    actual var weekdays: List<String>
        get() {
            val weekdaysWithEmptyFirst = symbols.weekdays.toList()
            return if (weekdaysWithEmptyFirst.size > 1) {
                weekdaysWithEmptyFirst.subList(1, weekdaysWithEmptyFirst.size)
            } else {
                emptyList()
            }
        }
        set(value) {
            updateSymbols {
                val weekdaysWithEmptyFirst = value.toMutableList().apply {
                    add(0, "")
                }
                it.weekdays = weekdaysWithEmptyFirst.toTypedArray()
            }
        }
    actual var shortWeekdays: List<String>
        get() {
            val weekdaysWithEmptyFirst = symbols.shortWeekdays.toList()
            return if (weekdaysWithEmptyFirst.size > 1) {
                weekdaysWithEmptyFirst.subList(1, weekdaysWithEmptyFirst.size)
            } else {
                emptyList()
            }
        }
        set(value) {
            updateSymbols {
                val weekdaysWithEmptyFirst = value.toMutableList().apply {
                    add(0, "")
                }
                it.shortWeekdays = weekdaysWithEmptyFirst.toTypedArray()
            }
        }

    actual var amString: String
        get() = symbols.amPmStrings.toList()[0]
        set(value) { updateSymbols { it.amPmStrings = it.amPmStrings.toMutableList().apply { this[0] = value }.toTypedArray() } }
    actual var pmString: String
        get() = symbols.amPmStrings.toList()[1]
        set(value) { updateSymbols { it.amPmStrings = it.amPmStrings.toMutableList().apply { this[1] = value }.toTypedArray() } }

    actual fun format(date: Date): String = format.format(date.calendar.time)
    actual fun parse(string: String): Date? {
        return try {
            format.parse(string)?.let { date ->
                val calendar = format.calendar.clone() as Calendar
                Date(calendar.apply { time = date })
            }
        } catch (e: ParseException) {
            null
        }
    }

    private fun updateSymbols(transform: (DateFormatSymbols) -> Unit) {
        val symbols = this.symbols
        transform(symbols)
        format.dateFormatSymbols = symbols
    }
}

private fun DateFormatStyle.javaStyle(): Int = when (this) {
    DateFormatStyle.Short -> DateFormat.SHORT
    DateFormatStyle.Medium -> DateFormat.MEDIUM
    DateFormatStyle.Long -> DateFormat.LONG
    DateFormatStyle.Full -> DateFormat.FULL
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Locale
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.text.ParseException
import java.util.Currency

@ExperimentalUnsignedTypes
actual class NumberFormatter actual constructor(actual val locale: Locale, style: NumberFormatStyle) {

    private val format: DecimalFormat = when (style) {
        is NumberFormatStyle.Integer -> DecimalFormat.getInstance(locale.locale).apply {
            minimumIntegerDigits = style.minDigits.toInt()
            maximumIntegerDigits = style.maxDigits.toInt()
            minimumFractionDigits = 0
            maximumFractionDigits = 0
        }
        is NumberFormatStyle.Decimal -> DecimalFormat.getInstance(locale.locale).apply {
            minimumIntegerDigits = style.minIntegerDigits.toInt()
            maximumIntegerDigits = style.maxIntegerDigits.toInt()
            minimumFractionDigits = style.minFractionDigits.toInt()
            maximumFractionDigits = style.maxFractionDigits.toInt()
        }
        is NumberFormatStyle.Percentage -> DecimalFormat.getPercentInstance(locale.locale).apply {
            minimumIntegerDigits = style.minIntegerDigits.toInt()
            maximumIntegerDigits = style.maxIntegerDigits.toInt()
            minimumFractionDigits = style.minFractionDigits.toInt()
            maximumFractionDigits = style.maxFractionDigits.toInt()
        }
        is NumberFormatStyle.Permillage -> {
            val pattern = (DecimalFormat.getPercentInstance(locale.locale) as DecimalFormat).toPattern().replace("%", "\u2030")
            DecimalFormat(pattern, DecimalFormatSymbols(locale.locale)).apply {
                minimumIntegerDigits = style.minIntegerDigits.toInt()
                maximumIntegerDigits = style.maxIntegerDigits.toInt()
                minimumFractionDigits = style.minFractionDigits.toInt()
                maximumFractionDigits = style.maxFractionDigits.toInt()
            }
        }
        is NumberFormatStyle.Scientific -> DecimalFormat(style.pattern, DecimalFormatSymbols(locale.locale))
        is NumberFormatStyle.Currency -> DecimalFormat.getCurrencyInstance(locale.locale).apply {
            style.currencyCode?.let { currencyCode ->
                currency = Currency.getInstance(currencyCode)
            }
            minimumIntegerDigits = style.minIntegerDigits.toInt()
            maximumIntegerDigits = style.maxIntegerDigits.toInt()
            minimumFractionDigits = style.minFractionDigits?.toInt() ?: currency?.defaultFractionDigits ?: 0
            maximumFractionDigits = style.maxFractionDigits?.toInt() ?: currency?.defaultFractionDigits ?: 0
        }
        is NumberFormatStyle.Pattern -> DecimalFormat("${style.positivePattern};${style.negativePattern}", DecimalFormatSymbols(locale.locale))
    } as DecimalFormat
    private val symbols: DecimalFormatSymbols get() = format.decimalFormatSymbols
    init {
        format.roundingMode = when (style.roundingMode) {
            RoundingMode.Ceiling -> java.math.RoundingMode.CEILING
            RoundingMode.Floor -> java.math.RoundingMode.FLOOR
            RoundingMode.HalfEven -> java.math.RoundingMode.HALF_EVEN
            RoundingMode.HalfUp -> java.math.RoundingMode.HALF_UP
            RoundingMode.HalfDown -> java.math.RoundingMode.HALF_DOWN
            RoundingMode.Down -> java.math.RoundingMode.DOWN
            RoundingMode.Up -> java.math.RoundingMode.UP
        }
    }

    actual var percentSymbol: Char
        get() = symbols.percent
        set(value) { applySymbols { it.percent = value } }
    actual var perMillSymbol: Char
        get() = symbols.perMill
        set(value) { applySymbols { it.perMill = value } }
    actual var minusSign: Char
        get() = symbols.minusSign
        set(value) { applySymbols { it.minusSign = value } }
    actual var exponentSymbol: String
        get() = symbols.exponentSeparator
        set(value) { applySymbols { it.exponentSeparator = value } }
    actual var zeroSymbol: Char
        get() = symbols.zeroDigit
        set(value) { applySymbols { it.zeroDigit = value } }
    actual var notANumberSymbol: String
        get() = symbols.naN
        set(value) { applySymbols { it.naN = value } }
    actual var infinitySymbol: String
        get() = symbols.infinity
        set(value) { applySymbols { it.infinity = value } }

    actual var currencySymbol: String
        get() = symbols.currencySymbol
        set(value) { applySymbols { it.currencySymbol = value } }
    actual var currencyCode: String
        get() = symbols.currency.currencyCode
        set(value) { applySymbols { it.currency = Currency.getInstance(value) } }

    actual var positivePrefix: String
        get() = format.positivePrefix
        set(value) { format.positivePrefix = value }
    actual var positiveSuffix: String
        get() = format.positiveSuffix
        set(value) { format.positiveSuffix = value }
    actual var negativePrefix: String
        get() = format.negativePrefix
        set(value) { format.negativePrefix = value }
    actual var negativeSuffix: String
        get() = format.negativeSuffix
        set(value) { format.negativeSuffix = value }

    actual var groupingSeparator: Char
        get() = symbols.groupingSeparator
        set(value) { applySymbols { it.groupingSeparator = value } }
    actual var usesGroupingSeparator: Boolean
        get() = format.isGroupingUsed
        set(value) { format.isGroupingUsed = value }
    actual var decimalSeparator: Char
        get() = symbols.decimalSeparator
        set(value) { applySymbols { it.decimalSeparator = value } }
    actual var alwaysShowsDecimalSeparator: Boolean
        get() = format.isDecimalSeparatorAlwaysShown
        set(value) { format.isDecimalSeparatorAlwaysShown = value }
    actual var currencyDecimalSeparator: Char
        get() = symbols.monetaryDecimalSeparator
        set(value) { applySymbols { it.monetaryDecimalSeparator = value } }
    actual var groupingSize: Int
        get() = format.groupingSize
        set(value) { format.groupingSize = value }
    actual var multiplier: Int
        get() = format.multiplier
        set(value) { format.multiplier = value }

    actual fun format(number: Number): String = format.format(number.toDouble())
    actual fun parse(string: String): Number? = try { format.parse(string) } catch (e: ParseException) { null }
    private fun applySymbols(apply: (DecimalFormatSymbols) -> Unit) {
        val symbols = format.decimalFormatSymbols
        apply(symbols)
        format.decimalFormatSymbols = symbols
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Locale

actual val lineSeparator = System.lineSeparator()

actual fun String.lowerCased(locale: Locale): String = this.lowercase(locale.locale)

actual fun String.upperCased(locale: Locale): String = this.uppercase(locale.locale)

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

import java.util.Calendar

actual class Date internal constructor(internal val calendar: Calendar) : Comparable<Date> {

    actual companion object {
        actual fun now(offsetInMilliseconds: Long, timeZone: TimeZone, locale: Locale): Date = Date(
            Calendar.getInstance(timeZone.timeZone, locale.locale).apply {
                add(Calendar.MILLISECOND, offsetInMilliseconds.toInt())
            }
        )
        actual fun epoch(offsetInMilliseconds: Long, timeZone: TimeZone, locale: Locale): Date = Date(
            Calendar.getInstance(timeZone.timeZone, locale.locale).apply {
                timeInMillis = offsetInMilliseconds
            }
        )
    }

    actual var timeZone: TimeZone
        get() = TimeZone(calendar.timeZone)
        set(value) { calendar.timeZone = value.timeZone }

    actual var era: Int
        get() = calendar.get(Calendar.ERA)
        set(value) { calendar.set(Calendar.ERA, value) }
    actual var year: Int
        get() = calendar.get(Calendar.YEAR)
        set(value) { calendar.set(Calendar.YEAR, value) }
    actual var month: Int
        get() = calendar.get(Calendar.MONTH) + 1
        set(value) { calendar.set(Calendar.MONTH, value - 1) }
    actual val daysInMonth: Int get() = calendar.getActualMaximum(Calendar.DAY_OF_MONTH)
    actual var weekOfYear: Int
        get() = calendar.get(Calendar.WEEK_OF_YEAR)
        set(value) { calendar.set(Calendar.WEEK_OF_YEAR, value) }
    actual var weekOfMonth: Int
        get() = calendar.get(Calendar.WEEK_OF_MONTH)
        set(value) { calendar.set(Calendar.WEEK_OF_MONTH, value) }
    actual var day: Int
        get() = calendar.get(Calendar.DAY_OF_MONTH)
        set(value) { calendar.set(Calendar.DAY_OF_MONTH, value) }
    actual var dayOfYear: Int
        get() = calendar.get(Calendar.DAY_OF_YEAR)
        set(value) { calendar.set(Calendar.DAY_OF_YEAR, value) }
    actual var weekDay: Int
        get() = calendar.get(Calendar.DAY_OF_WEEK)
        set(value) { calendar.set(Calendar.DAY_OF_WEEK, value) }
    actual var firstWeekDay: Int
        get() = calendar.firstDayOfWeek
        set(value) { calendar.firstDayOfWeek = value }

    actual var hour: Int
        get() = calendar.get(Calendar.HOUR_OF_DAY)
        set(value) { calendar.set(Calendar.HOUR_OF_DAY, value) }
    actual var minute: Int
        get() = calendar.get(Calendar.MINUTE)
        set(value) { calendar.set(Calendar.MINUTE, value) }
    actual var second: Int
        get() = calendar.get(Calendar.SECOND)
        set(value) { calendar.set(Calendar.SECOND, value) }
    actual var millisecond: Int
        get() = calendar.get(Calendar.MILLISECOND)
        set(value) { calendar.set(Calendar.MILLISECOND, value) }
    actual var millisecondSinceEpoch: Long
        get() = calendar.timeInMillis
        set(value) { calendar.timeInMillis = value }

    actual fun copy(): Date = Date(calendar.clone() as Calendar)

    actual override fun equals(other: Any?): Boolean {
        return (other as? Date)?.let {
            timeZone == other.timeZone && millisecondSinceEpoch == other.millisecondSinceEpoch
        } ?: false
    }

    override fun compareTo(other: Date): Int {
        return this.calendar.time.compareTo(other.calendar.time)
    }

    actual override fun hashCode(): Int {
        return calendar.hashCode()
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.

 */

@file:JvmName("DecimalAndroid")
package com.splendo.kaluga.base.utils

import com.splendo.kaluga.base.utils.RoundingMode.RoundDown
import com.splendo.kaluga.base.utils.RoundingMode.RoundHalfEven
import com.splendo.kaluga.base.utils.RoundingMode.RoundUp
import java.math.BigDecimal
import java.math.MathContext
import java.math.RoundingMode as NativeRoundingMode

actual typealias Decimal = BigDecimal

actual operator fun Decimal.plus(value: Decimal) = this.add(value)

actual fun Decimal.plus(value: Decimal, scale: Int) =
    this.add(value).setScale(scale, NativeRoundingMode.HALF_EVEN)

actual fun Decimal.plus(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode
) = this.add(value).setScale(scale, roundingMode.android)

actual operator fun Decimal.minus(value: Decimal) = this.subtract(value)

actual fun Decimal.minus(value: Decimal, scale: Int) =
    this.subtract(value).setScale(scale, NativeRoundingMode.HALF_EVEN)

actual fun Decimal.minus(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode
) = this.subtract(value).setScale(scale, roundingMode.android)

actual operator fun Decimal.div(value: Decimal) = this.divide(value, MathContext.DECIMAL128)

actual fun Decimal.div(value: Decimal, scale: Int) =
    this.divide(value, MathContext.DECIMAL128).setScale(scale, NativeRoundingMode.HALF_EVEN)

actual fun Decimal.div(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode
) = this.divide(
    value,
    MathContext(
        MathContext.DECIMAL128.precision,
        NativeRoundingMode.valueOf(roundingMode.android)
    )
).setScale(scale, roundingMode.android)

actual operator fun Decimal.times(value: Decimal) =
    this.multiply(value, MathContext.DECIMAL128)

actual fun Decimal.times(value: Decimal, scale: Int) =
    this.multiply(value, MathContext.DECIMAL128).setScale(scale, NativeRoundingMode.HALF_EVEN)

actual fun Decimal.times(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode
) = this.multiply(
    value,
    MathContext(
        MathContext.DECIMAL128.precision,
        NativeRoundingMode.valueOf(roundingMode.android)
    )
).setScale(scale, NativeRoundingMode.valueOf(roundingMode.android))

actual fun Decimal.round(scale: Int, roundingMode: RoundingMode) =
    this.setScale(
        scale,
        NativeRoundingMode.valueOf(roundingMode.android)
    )

actual infix fun Decimal.pow(n: Int): Decimal = this.pow(n, MathContext.DECIMAL128)
actual fun Decimal.pow(n: Int, scale: Int): Decimal = this.pow(n, MathContext.DECIMAL128).setScale(scale, NativeRoundingMode.HALF_EVEN)
actual fun Decimal.pow(
    n: Int,
    scale: Int,
    roundingMode: RoundingMode
): Decimal = this.pow(
    n,
    MathContext(
        MathContext.DECIMAL128.precision,
        NativeRoundingMode.valueOf(roundingMode.android)
    )
).setScale(scale, NativeRoundingMode.valueOf(roundingMode.android))

actual fun Number.toDecimal() = BigDecimal(this.toString())
actual fun String.toDecimal() = BigDecimal(this)

actual fun Decimal.toDouble() = this.toDouble()
actual fun Decimal.toInt() = this.toInt()
actual fun Decimal.toString() = this.stripTrailingZeros().toString()

val RoundingMode.android
    get() = when (this) {
        RoundDown -> NativeRoundingMode.DOWN.ordinal
        RoundHalfEven -> NativeRoundingMode.HALF_EVEN.ordinal
        RoundUp -> NativeRoundingMode.UP.ordinal
    }

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

import android.icu.util.LocaleData
import android.icu.util.ULocale
import android.os.Build
import com.splendo.kaluga.base.text.upperCased

actual class Locale internal constructor(val locale: java.util.Locale) {
    actual companion object {
        actual fun createLocale(language: String): Locale = Locale(java.util.Locale(language))
        actual fun createLocale(language: String, country: String): Locale = Locale(java.util.Locale(language, country))
        actual fun createLocale(language: String, country: String, variant: String): Locale = Locale(java.util.Locale(language, country, variant))

        actual val defaultLocale: Locale get() = Locale(java.util.Locale.getDefault())
        actual val availableLocales: List<Locale> = java.util.Locale.getAvailableLocales().asList().map { Locale(it) }
    }

    actual val countryCode: String
        get() = locale.country
    actual val languageCode: String
        get() = locale.language
    actual val scriptCode: String
        get() = locale.script
    actual val variantCode: String
        get() = locale.variant
    actual val unitSystem: UnitSystem
        get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            when (LocaleData.getMeasurementSystem(ULocale.forLocale(locale))) {
                LocaleData.MeasurementSystem.US -> UnitSystem.IMPERIAL
                LocaleData.MeasurementSystem.UK -> UnitSystem.MIXED
                else -> UnitSystem.METRIC
            }
        } else {
            UnitSystem.withCountryCode(countryCode.upperCased(this))
        }

    actual fun name(forLocale: Locale): String = locale.getDisplayName(forLocale.locale)
    actual fun countryName(forLocale: Locale): String = locale.getDisplayCountry(forLocale.locale)
    actual fun languageName(forLocale: Locale): String = locale.getDisplayLanguage(forLocale.locale)
    actual fun variantName(forLocale: Locale): String = locale.getDisplayVariant(forLocale.locale)
    actual fun scriptName(forLocale: Locale): String = locale.getDisplayScript(forLocale.locale)

    actual val quotationStart: String
        get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            LocaleData.getInstance(ULocale.forLocale(locale)).getDelimiter(LocaleData.QUOTATION_START)
        } else {
            "\""
        }
    actual val quotationEnd: String
        get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            LocaleData.getInstance(ULocale.forLocale(locale)).getDelimiter(LocaleData.QUOTATION_END)
        } else {
            "\""
        }
    actual val alternateQuotationStart: String
        get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            LocaleData.getInstance(ULocale.forLocale(locale)).getDelimiter(LocaleData.ALT_QUOTATION_START)
        } else {
            "\""
        }
    actual val alternateQuotationEnd: String
        get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            LocaleData.getInstance(ULocale.forLocale(locale)).getDelimiter(LocaleData.ALT_QUOTATION_END)
        } else {
            "\""
        }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils
import java.io.Serializable

actual typealias Serializable = Serializable

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

actual class TimeZone internal constructor(internal val timeZone: java.util.TimeZone) {

    actual companion object {
        actual fun get(identifier: String): TimeZone? {
            return java.util.TimeZone.getTimeZone(identifier)?.let {
                TimeZone(it)
            }
        }
        actual fun current(): TimeZone {
            return TimeZone(java.util.TimeZone.getDefault())
        }
        actual val availableIdentifiers get() = java.util.TimeZone.getAvailableIDs().asList()
    }

    actual val identifier: String = timeZone.id
    actual fun displayName(style: TimeZoneNameStyle, withDaylightSavings: Boolean, locale: Locale): String {
        val styleJava = when (style) {
            TimeZoneNameStyle.Short -> java.util.TimeZone.SHORT
            TimeZoneNameStyle.Long -> java.util.TimeZone.LONG
        }
        return timeZone.getDisplayName(withDaylightSavings, styleJava, locale.locale)
    }
    actual val offsetFromGMTInMilliseconds = timeZone.rawOffset.toLong()
    actual val daylightSavingsOffsetInMilliseconds: Long = timeZone.dstSavings.toLong()
    actual fun offsetFromGMTAtDateInMilliseconds(date: Date): Long = timeZone.getOffset(date.millisecondSinceEpoch).toLong()
    actual fun usesDaylightSavingsTime(date: Date): Boolean = timeZone.inDaylightTime(date.calendar.time)
    actual fun copy(): TimeZone = TimeZone(timeZone.clone() as java.util.TimeZone)
    override fun equals(other: Any?): Boolean {
        return (other as? TimeZone)?.let { timeZone == other.timeZone } ?: false
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.flow

import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.logging.warn
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.first

/**
 * Base definition of [Flowable]. Abstract class, use [ColdFlowable] or [HotFlowable] instead.
 *
 * @param T the value type to flow on.
 * @param channelFactory Factory for generating a [BroadcastChannel] on which the data is flown
 */
@Deprecated("This Channel based implementation is deprecated in favor of SharedFlow or StateFlow")
abstract class BaseFlowable<T>(private val channelFactory: () -> BroadcastChannel<T> = { ConflatedBroadcastChannel() }) : Flowable<T> {

    private val channel: AtomicReference<BroadcastChannel<T>?> = AtomicReference(null)
    protected fun ensureChannel(): BroadcastChannel<T> {
        return channel.get() ?: channelFactory().also { channel.set(it) }
    }

    override fun flow(flowConfig: FlowConfig): Flow<T> {
        val channel = ensureChannel()
        if (channel.isClosedForSend)
            error("channelFactory returned a channel closed for sending")
        return channel.asFlow().let { flowConfig.apply(it) }
    }

    override suspend fun set(value: T) {
        channel.get()?.send(value) ?: warn("'$value' offered to Flowable but there is no channel active")
    }

    override fun cancelFlows() {
        channel.get()?.let {
            it.close()
            channel.compareAndSet(it, null)
        }
    }

    // Note: if the channel is buffered, this could be wrong.
    // Since Flowable is deprecated this will not be fixed
    protected suspend fun currentValue(): T? = channel.get()?.asFlow()?.first()
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.flow

import co.touchlab.stately.concurrency.AtomicInt
import com.splendo.kaluga.flow.BaseFlowable
import com.splendo.kaluga.flow.FlowConfig
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

/**
 * A [BaseFlowable] that represents a Cold flow. This flowable will only become active once observed and deinitialises once no observers are present.
 * Data can only be set when the flowable has had at least one observer and was never closed.
 *
 * @param T the type of the value to flow on
 * @param initialize method for determining the initial value of the flow. Will be called when the flow transitions from zero to one or more observers.
 * @param deinitialize method for deinitializing the flow, passing the last known value. Will be called when the flow transitions from one or more to zero observers.
 * @param channelFactory Factory for generating a [BroadcastChannel] on which the data is flown
 */
@Deprecated("Use SharedFlowable or StateFlowable")
class ColdFlowable<T>(private val initialize: suspend () -> T, private val deinitialize: suspend (T) -> Unit, channelFactory: () -> BroadcastChannel<T> = { ConflatedBroadcastChannel() }) : BaseFlowable<T>(channelFactory) {

    private val counterMutex = Mutex()
    private var flowingCounter = AtomicInt(0)

    override fun flow(flowConfig: FlowConfig): Flow<T> {
        return super.flow(flowConfig)
            .onStart {
                counterMutex.withLock {
                    val count = flowingCounter.incrementAndGet()
                    if (count == 1) {
                        set(initialize())
                    }
                }
            }
            .onCompletion {
                counterMutex.withLock {
                    val count = flowingCounter.decrementAndGet()
                    if (count == 0) {
                        val finalValue = currentValue()
                        cancelFlows()
                        finalValue?.let {
                            deinitialize(finalValue)
                        }
                    }
                }
            }
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.flow

import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.conflate

@Deprecated("Flowable is deprecated")
typealias FlowModifier = (Flow<*>) -> Unit

@Deprecated("Flowable is deprecated")
sealed class FlowConfig(val config: FlowModifier) {

    object None : FlowConfig({})
    object Conflate : FlowConfig({ it.conflate() })
    object Infinite : FlowConfig({ it.buffer(Channel.UNLIMITED) })
    class Custom(options: FlowModifier) : FlowConfig(options)

    fun <T, F : Flow<T>> apply(flow: F): F {
        config(flow)
        return flow
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.flow

import com.splendo.kaluga.base.runBlocking
import kotlinx.coroutines.flow.Flow

/**
 * An object that be turned into a multi channel [Flow]
 *
 * @param T the type of the values emitted by the flowable.
 */
@Deprecated("Use MutableSharedFlow or MutableStateFlow, with onCollectionEvent", replaceWith = ReplaceWith("MutableSharedFlow"))
interface Flowable<T> {

    /**
     * Generates a [Flow] for this Flowable
     *
     * @param flowConfig the [FlowConfig] to apply to this Flow
     * @return A [Flow] of values set to the Flowable
     *
     */
    fun flow(flowConfig: FlowConfig = FlowConfig.Conflate): Flow<T>

    /**
     * Stops the flowable from being observed. Cancels all active [Flow]s.
     * Starting a new Flow is possible however.
     */
    fun cancelFlows()

    /**
     * Sets the value of the Flowable. All active [Flow]s should be notified
     *
     * @param value the value to set
     */
    suspend fun set(value: T)

    /**
     * Applies [set] in a blocking coroutine
     */
    fun setBlocking(value: T) = runBlocking { set(value) }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.flow

import com.splendo.kaluga.flow.BaseFlowable
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.ConflatedBroadcastChannel

/**
 * A [BaseFlowable] that represents a Hot flow. This flowable will contain data even if not observed.
 *
 * @param T the type of the value to flow on
 * @param initialValue the initial value of the flow
 * @param channelFactory Factory for generating a [BroadcastChannel] on which the data is flown
 */
@Deprecated("This channel based implementation of Flowable is deprecated in favor of SharedFlowable or StateFlowable")
class HotFlowable<T> (val initialValue: T, channelFactory: () -> BroadcastChannel<T> = { ConflatedBroadcastChannel() }) : BaseFlowable<T>(channelFactory) {

    init {
        ensureChannel()
        setBlocking(initialValue)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.flow

import com.splendo.kaluga.base.flow.SharedFlowCollectionEvent.FirstCollection
import com.splendo.kaluga.base.flow.SharedFlowCollectionEvent.LaterCollections
import com.splendo.kaluga.base.flow.SharedFlowCollectionEvent.NoMoreCollections
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.dropWhile
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.transformWhile
import kotlinx.coroutines.flow.withIndex

enum class SharedFlowCollectionEvent {
    NoMoreCollections, FirstCollection, LaterCollections
}

/**
 * Calls back with events in the following order:
 *
 * [FirstCollection] -> [NoMoreCollections] -> [LaterCollections] -> back to [NoMoreCollections] when all flows are terminated
 *
 * If a flow already exists on this [MutableSharedFlow] the [FirstCollection] event is immediately raised.
 * If no subscriptions exist
 *
 * Note that if this [MutableSharedFlow] was subscribed and unsubscribed before this method was called,
 * a new subscription will still raise the [FirstCollection] event
 */
suspend fun <T> MutableSharedFlow<T>.onCollectionEvent(onEvent: suspend MutableSharedFlow<T>.(SharedFlowCollectionEvent) -> Unit): Unit =
    subscriptionCount
        .map { it > 0 } // simplify to zero (false) or one-or-more (true)
        .dropWhile { !it } // do not report until the first collection
        .distinctUntilChanged() // report as state not an event
        .withIndex() // add an index so we distinguish the first event
        .collect { subscriptionState ->
            onEvent(
                when {
                    subscriptionState.value && subscriptionState.index == 0 -> FirstCollection
                    subscriptionState.value && subscriptionState.index != 0 -> LaterCollections
                    else -> NoMoreCollections
                }
            )
        }

/**
 * These interfaces can be used to mark object you emit to a Flow as a special value.
 *
 * Extension methods to Flow can be used to filter and/or terminate on these values.
 */
sealed class SpecialFlowValue { // TODO: this can be a sealed interface one day

    /**
     * Indicates this value is the last in a series.
     * This is useful in particular to terminate collection from endless flows such as SharedFlow and StateFlow.
     */
    interface Last

    /**
     * Indicates the value is less meaningful (e.g. an initial state with no value yet) that can usually be skipped
     */
    interface NotImportant
}

fun <T> Flow<T>.takeUntilLast(includeLast: Boolean = true): Flow<T> = transformWhile {
    val notLast = it !is SpecialFlowValue.Last
    if (notLast || includeLast)
        emit(it)
    notLast
}

@Suppress("NOTHING_TO_INLINE") // copies from filter()
inline fun <T> Flow<T>.filterOnlyImportant(): Flow<T> =
    filterNot { it is SpecialFlowValue.NotImportant }

suspend inline fun <T> Flow<T>.collectUntilLast(
    includeLast: Boolean = true,
    crossinline action: suspend (value: T) -> Unit
) =
    takeUntilLast(includeLast).collect(object : FlowCollector<T> {
        override suspend fun emit(value: T) = action(value)
    }
    )

suspend inline fun <T> Flow<T>.collectImportant(crossinline action: suspend (value: T) -> Unit) =
    filterOnlyImportant().collect(object : FlowCollector<T> {
        override suspend fun emit(value: T) = action(value)
    }
    )

suspend inline fun <T> Flow<T>.collectImportantUntilLast(
    includeLast: Boolean = true,
    crossinline action: suspend (value: T) -> Unit
) =
    takeUntilLast(includeLast).filterOnlyImportant().collect(object : FlowCollector<T> {
        override suspend fun emit(value: T) = action(value)
    }
    )

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base

import com.splendo.kaluga.logging.debug
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow

interface ServiceMonitor {
    val isServiceEnabled: Boolean
    val isEnabled: Flow<Boolean>
    fun startMonitoring()
    fun stopMonitoring()
}

abstract class DefaultServiceMonitor : ServiceMonitor {

    protected val TAG: String = this::class.simpleName ?: "ServiceMonitor"

    protected val _isEnabled = MutableStateFlow(isServiceEnabled)
    override val isEnabled = _isEnabled.asStateFlow()

    override fun startMonitoring() {
        debug(TAG) { "Start monitoring service state ($isServiceEnabled)" }
        updateState()
    }
    override fun stopMonitoring() {
        debug(TAG) { "Stop monitoring service state ($isServiceEnabled)" }
        updateState()
    }

    protected fun updateState() {
        debug(TAG) { "updateState isLocationEnabled = $isServiceEnabled" }
        _isEnabled.value = isServiceEnabled
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.state

import co.touchlab.stately.concurrency.AtomicBoolean
import com.splendo.kaluga.base.flow.SharedFlowCollectionEvent.FirstCollection
import com.splendo.kaluga.base.flow.SharedFlowCollectionEvent.LaterCollections
import com.splendo.kaluga.base.flow.SharedFlowCollectionEvent.NoMoreCollections
import com.splendo.kaluga.base.flow.onCollectionEvent
import com.splendo.kaluga.base.runBlocking
import com.splendo.kaluga.base.utils.EmptyCompletableDeferred
import com.splendo.kaluga.base.utils.complete
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlin.coroutines.CoroutineContext
import kotlin.native.concurrent.SharedImmutable
import kotlin.reflect.KClass

@SharedImmutable
private val remain: suspend() -> State = { error("This should never be called. It's only used to indicate the state should remain the same") }

/**
 * State to be represented in a state machine
 */
open class State {

    /**
     * Use this to indicate to the state machine the state should stay the same
     * @return a special continuation that will be recognized by the state machine. Running this continuation will cause an error.
     */
    @Suppress("UNCHECKED_CAST") // cast should normally work since the receiver uses one type of state
    fun <S : State> remain(): suspend() -> S = remain as suspend () -> S

    /**
     * Called when this state is the first state of the state machine
     */
    @Deprecated("Use an initializer state rather than relying on this method, it might be called after the initial state is already changed")
    open suspend fun initialState() {}

    /**
     * Called when this state is the final state of the state machine
     */
    @Deprecated("This method is not always actually called (e.g. for a HotRepo) since there is not always a final state")
    open suspend fun finalState() {}
}

interface HandleBeforeCreating {
    /**
     * Called while transitioning to a new state before the new state is created
     */
    suspend fun beforeCreatingNewState()
}

interface HandleAfterCreating<S : State> {
    /**
     * Called while transitioning to a new state after the new state is created
     *
     * @param newState the newly created state
     */
    suspend fun afterCreatingNewState(newState: S)
}

interface HandleAfterNewStateIsSet<S : State> {

    /**
     * Called while transitioning to a new state after the new state is set.
     *
     * @param newState the newly set [State]
     */
    suspend fun afterNewStateIsSet(newState: S)
}

interface HandleBeforeOldStateIsRemoved<S : State> {

    /**
     * Called while transitioning from an old state before it is removed.
     *
     * @param oldState the [State] to be removed
     */
    suspend fun beforeOldStateIsRemoved(oldState: S)
}

interface HandleAfterOldStateIsRemoved<S : State> {

    /**
     * Called while transitioning from an old state after it is removed
     *
     * @param oldState the removed state
     */
    suspend fun afterOldStateIsRemoved(oldState: S)
}

/**
 * The state repo can change holds the current [State] (which can be accessed as a flow), and can be used to change the current state
 *
 * @param S the [State] represented by this repo.
 * @param coroutineContext the [CoroutineContext] used to create a coroutine scope for this state machine. Make sure that if you pass a coroutine context that has sequential execution if you do not want simultaneous state changes. The default Main dispatcher meets these criteria.
 */
abstract class StateRepo<S : State, F : MutableSharedFlow<S>>(coroutineContext: CoroutineContext = Dispatchers.Main.immediate) : CoroutineScope by CoroutineScope(coroutineContext + CoroutineName("State Repo")), Flow<S> {

    override suspend fun collect(collector: FlowCollector<S>) = mutableFlow.collect(collector)

    /**
     * Semaphore used as a mutex for changing state.
     *
     * By default the single permit is acquired, only when the initial state is set a release is done.
     */
    private val stateMutex = Semaphore(1, 1)

    protected abstract val mutableFlow: F
    val subscriptionCount
        get() = mutableFlow.subscriptionCount

    /**
     * Provides a [Flow] of the [State] of this repo.
     *
     * @return The [Flow]
     */
    @Deprecated(message = "StateRepo itself is now a Flow", replaceWith = ReplaceWith("StateRepo"))
    fun flow(): Flow<S> = mutableFlow.asSharedFlow()

    private val initialized = AtomicBoolean(false)

    internal open suspend fun initialize(initialValue: S? = null): S =
        if (initialized.compareAndSet(false, true))
            (initialValue ?: initialValue()).also { value ->
                mutableFlow.emit(value)
                stateMutex.release() // release the initial permit held
                // The mutex is already released above to let the state initialize afterwards, to avoid changeState mutex deadlocks
                // However releasing the above mutex might have already changed the state to a new state before initialState runs.
                // State machines that need initialization should rely on having an initialization state rather than using this method.
                value.initialState()
            }
        else
            state()

    /**
     * Gets the initial value of the repo
     * @return the initial value of the repo
     */
    abstract suspend fun initialValue(): S

    internal suspend fun state(): S {
        // TODO:
        // if this state machine is backed by a SharedFlow instead of a pure StateFlow this will suspend indefinitely if no state is set
        // this only occurs (normally) if the initial state is not set.
        //
        // Perhaps an alternate way of throwing the exception (e.g. a boolean flag for the first emit) would be good,
        // however this does not guard manipulation of the SharedFlow before it is passed (e.g. MutableStateFlow's default value)
        // or directly on the flow (currently exposed to subclasses as a protected field)
        //
        // in the meanwhile, there might also be legitimate use cases for suspending until first state.
        // So for now at least, we accept this possible deadlock
        //
        // The replay cache instantiation is somewhat heavy but there is no method to get only the the last entry in the cache
        // if no or a small replayCache is used it is not so bad. Also is a StateFlow is used, this problem does not occur.
        return (mutableFlow as? StateFlow<S>)?.value ?: mutableFlow.replayCache.lastOrNull() ?: mutableFlow.first()
    }

    /**
     * Peek the current state of the state machine. The current state could change immediately after it is returned.
     *
     * Also no state could be set yet, in which case this method will block. Use [useState] for a suspending alternative.
     *
     * If any actions are taken based on the current state that affect the state machine you should not use this method.
     *
     * If your code relies on the state not changing use [useState].
     * If you want to change the state based on the current state use [takeAndChangeState]
     *
     * @return the current [State] of the [StateRepo]
     */
    fun peekState() = runBlocking {
        initialize()
        state()
    }

    /**
     * Makes the current [State] available in [action]. The state is guaranteed not to change during the execution of [action].
     * This operation ensures atomic state observations, so the state will not change while the [action] is being executed.
     *
     * This method uses a separate coroutineScope, meaning it will suspend until all child Jobs are completed, including those that asynchronously call this method itself (however a different state might be current at that point).
     *
     * @param action the function for which will [State] receive the state, guaranteed to be unchanged for the duration of the function.
     */
    suspend fun useState(action: suspend (S) -> Unit) = coroutineScope {
        initialize()
        stateMutex.withPermit {
            val result = EmptyCompletableDeferred()
            launch {
                try {
                    action(state())
                    result.complete()
                } catch (e: Throwable) {
                    result.completeExceptionally(e)
                }
            }
            return@coroutineScope result.await()
        }
    }

    fun launchUseState(
        context: CoroutineContext = coroutineContext,
        action: suspend(S) -> Unit
    ) = launch(context) {
        useState(action)
    }

    suspend fun takeAndChangeState(action: suspend(S) -> suspend () -> S) =
        doTakeAndChangeState(remainIfStateNot = null, action)

    /**
     * Changes from the current [State] to a new [State]. This operation ensures atomic state changes.
     * The new state is determined by an [action], which takes the current [State] upon starting the state transition and provides a deferred state creation.
     * You are strongly encouraged to use the [State] provided by the [action] to determine the new state, to ensure no illegal state transitions occur, as the state may have changed between calling [doTakeAndChangeState] and the execution of [action].
     * If the [action] returns [State.remain] no state transition will occur.
     * Since this operation is atomic, the [action] should not directly call [doTakeAndChangeState] itself. If required to do this, handle the additional transition in a separate coroutine.
     *
     * This method uses a separate coroutineScope, meaning it will suspend until all child Jobs are completed, including those that asynchronously call this method itself.
     *
     * @param remainIfStateNot If the current state at the time of Action is not an instance of this class, the state will automatically remain.
     * @param action Function to determine the [State] to be transitioned to from the current [State]. If no state transition should occur, return [State.remain]
     */
    suspend fun <K : S> takeAndChangeState(remainIfStateNot: KClass<K>, action: suspend(K) -> suspend () -> S) =
        doTakeAndChangeState(remainIfStateNot) {
            action(it)
        }

    fun <K : S> launchTakeAndChangeState(
        context: CoroutineContext = coroutineContext,
        remainIfStateNot: KClass<K>,
        action: suspend(K) -> suspend () -> S
    ) = launch(context) {
        takeAndChangeState(remainIfStateNot) {
            action(it)
        }
    }

    fun launchTakeAndChangeState(
        context: CoroutineContext = coroutineContext,
        action: suspend(S) -> suspend () -> S
    ) = launch(context) {
        takeAndChangeState(action)
    }

    private suspend inline fun <K : S> doTakeAndChangeState(remainIfStateNot: KClass<K>?, crossinline action: suspend(K) -> suspend () -> S): S = coroutineScope { // scope around the mutex so asynchronously scheduled coroutines that also use this method can run before the scope completed without deadlocks
        initialize()
        stateMutex.withPermit {
            val result = CompletableDeferred<S>()
            launch {
                try {
                    val beforeState = state()
                    // There are only two methods calling this private method.
                    // either K is the same as S (no `remainIfStateNot` parameter), or we do the isInstance check
                    @Suppress("UNCHECKED_CAST")
                    val transition = // if remainIfNot was passes, only execute action if the beforeState matches
                        when {
                            remainIfStateNot == null || remainIfStateNot.isInstance(beforeState) -> action(beforeState as K)
                            else -> beforeState.remain() // else just remain
                        }

                    if (beforeState.remain<S>() === transition) {
                        result.complete(beforeState)
                    } else {
                        (beforeState as? HandleBeforeCreating)?.beforeCreatingNewState()
                        val newState = transition()
                        (beforeState as? HandleAfterCreating<S>)?.afterCreatingNewState(newState)
                        (newState as? HandleBeforeOldStateIsRemoved<S>)?.beforeOldStateIsRemoved(beforeState)
                        mutableFlow.emit(newState)
                        (beforeState as? HandleAfterNewStateIsSet<S>)?.afterNewStateIsSet(newState)
                        (newState as? HandleAfterOldStateIsRemoved<S>)?.afterOldStateIsRemoved(beforeState)
                        result.complete(newState)
                    }
                } catch (t: Throwable) {
                    result.completeExceptionally(t)
                }
            }
            return@coroutineScope result.await()
        }
    }
}

@Suppress("NOTHING_TO_INLINE")
// Somewhat similar to a ConflatedBroadcastChannel, which was used in the previous implementation
inline fun <S> defaultLazySharedFlow(): Lazy<MutableSharedFlow<S>> = lazy { MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) }

interface StateFlowRepo<S : State> {
    val stateFlow: StateFlow<S>
}

/**
 * A [StateRepo] that represents its [State] as a Hot flow.
 */
abstract class HotStateRepo<S : State>(coroutineContext: CoroutineContext = Dispatchers.Main.immediate) :
    BaseHotStateRepo<S, MutableSharedFlow<S>>(coroutineContext) {
    final override val lazyMutableSharedFlow: Lazy<MutableSharedFlow<S>> = defaultLazySharedFlow()
}

abstract class HotStateFlowRepo<S : State>(
    coroutineContext: CoroutineContext = Dispatchers.Main.immediate,
    val initialState: (HotStateFlowRepo<S>) -> S
) : StateFlowRepo<S>,
    BaseHotStateRepo<S, MutableStateFlow<S>>(coroutineContext) {

    override val lazyMutableSharedFlow = lazy { MutableStateFlow(initialState(this)) }

    override val stateFlow
        get() = mutableFlow.asStateFlow()

    final override suspend fun initialValue(): S = mutableFlow.value
}

abstract class BaseHotStateRepo<S : State, F : MutableSharedFlow<S>>(
    coroutineContext: CoroutineContext = Dispatchers.Main.immediate
) : StateRepo<S, F>(coroutineContext) {

    abstract val lazyMutableSharedFlow: Lazy<F>

    // guards once only initialization across threads
    private val initialized = AtomicBoolean(false)

    override val mutableFlow: F
        get() {
            val isInitialized = lazyMutableSharedFlow.isInitialized()
            val flow = lazyMutableSharedFlow.value
            if (!isInitialized && initialized.compareAndSet(expected = false, new = true))
                launch(coroutineContext) {
                    initialize()
                }
            return flow
        }
}

/**
 * A Cold StateRepo, using a MutableSharedFlow as it's backing implementation.
 *
 * The repo will get initialized when the state is first read.
 *
 * There are then subsequent events for first collections, when there are no more collections, and later collections. Also see [onCollectionEvent].
 *
 * Be aware an initialization can take place if state is read, for example by [useState] or [takeAndChangeState], without actual collection events occurring.
 * However a SharedFlow without a replay buffer might trigger a collection to get the initial state if no explicit state is provided
 */
abstract class BaseColdStateRepo<S : State, F : MutableSharedFlow<S>>(
    context: CoroutineContext = Dispatchers.Main.immediate
) : StateRepo<S, F>(context) {

    // guards once only initialization across threads
    private val initialized = AtomicBoolean(false)

    abstract val lazyMutableFlow: Lazy<F>

    override val mutableFlow: F
        get() {
            val isInitialized = lazyMutableFlow.isInitialized()
            val flow = lazyMutableFlow.value
            if (!isInitialized && initialized.compareAndSet(expected = false, new = true)) {
                launch(coroutineContext) {
                    flow.onCollectionEvent { event ->
                        when (event) {
                            NoMoreCollections -> noMoreCollections().also { it.finalState() }
                            FirstCollection -> firstCollection()
                            LaterCollections -> laterCollections().also { it.initialState() }
                        }
                    }
                }
            }
            return flow
        }

    abstract suspend fun firstCollection()

    abstract suspend fun laterCollections(): S

    abstract suspend fun noMoreCollections(): S
}

/**
 * A ColdStateRepo based on a StateFlow.
 *
 * This implementation allows for full fledged state transitions when the repo gains it's first subscriber
 * or loses all of it's subscribers.
 *
 * It also has an optional initial state.
 */
open class ColdStateFlowRepo<S : State>(
    coroutineContext: CoroutineContext = Dispatchers.Main.immediate,
    val initChangeStateWithRepo: suspend (S?, ColdStateFlowRepo<S>) -> (suspend () -> S),
    val deinitChangeStateWithRepo: suspend (S, ColdStateFlowRepo<S>) -> (suspend () -> S)?,
    val firstState: (suspend() -> S)? = null
) : StateFlowRepo<S>,
    BaseColdStateRepo<S, MutableStateFlow<S>>(
        context = coroutineContext,
    ) {

    constructor(
        coroutineContext: CoroutineContext = Dispatchers.Main.immediate,
        // order is different than below because here firstState is mandatory, and to avoid JVM signature clashes
        firstState: suspend() -> S,
        initChangeState: suspend (S) -> (suspend () -> S),
        deinitChangeState: suspend (S) -> (suspend () -> S)
    ) : this(
        coroutineContext,
        initChangeStateWithRepo = { state, _ -> state?.let { initChangeState(state) } ?: firstState },
        deinitChangeStateWithRepo = { state, _ -> deinitChangeState(state) },
        firstState = firstState
    )

    constructor(
        coroutineContext: CoroutineContext = Dispatchers.Main.immediate,
        init: suspend (ColdStateFlowRepo<S>) -> S,
        deinit: suspend (ColdStateFlowRepo<S>) -> S?,
        firstState: (suspend() -> S)? = null
    ) : this(
        coroutineContext,
        initChangeStateWithRepo = { _, repo -> { init(repo) } },
        deinitChangeStateWithRepo = { state, repo -> { deinit(repo) ?: state } },
        firstState = firstState
    )

    override val stateFlow: StateFlow<S>
        get() = mutableFlow.asStateFlow()

    // the first initialization is done in the lazy block below since StateFlow requires an initial value
    final override suspend fun initialValue(): S = mutableFlow.value

    final override suspend fun noMoreCollections() = takeAndChangeState { state ->
        deinitChangeStateWithRepo(state, this) ?: state.remain()
    }

    override val lazyMutableFlow: Lazy<MutableStateFlow<S>> =
        lazy {
            runBlocking {
                MutableStateFlow(
                    firstState?.invoke() ?: initChangeStateWithRepo(null, this@ColdStateFlowRepo)()
                )
            }
        }

    override suspend fun firstCollection() {
        laterCollections()
    }

    override suspend fun laterCollections() = takeAndChangeState { state ->
        initChangeStateWithRepo(state, this@ColdStateFlowRepo)
    }
}

/**
 * A [StateRepo] that represents its [State] as a Cold flow. Data will only be set when the state is observed.
 *
 * This class uses a very simple initialize and deinitialize pattern without changes of state by default
 *
 * This implementation uses a [MutableSharedFlow]. If you want to use a cold state repo based on StateFlow,
 * consider [ColdStateFlowRepo]
 */
abstract class ColdStateRepo<S : State>(
    coroutineContext: CoroutineContext = Dispatchers.Main.immediate,
    override val lazyMutableFlow: Lazy<MutableSharedFlow<S>> = defaultLazySharedFlow()
) : BaseColdStateRepo<S, MutableSharedFlow<S>>(coroutineContext) {

    final override suspend fun firstCollection() {
        initialize()
    }

    final override suspend fun laterCollections() = takeAndChangeState {
        { initialValue() }
    }

    final override suspend fun noMoreCollections(): S = takeAndChangeState {
        deinitialize(it)
        it.remain()
    }

    abstract suspend fun deinitialize(state: S)
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Date
import com.splendo.kaluga.base.utils.Locale
import com.splendo.kaluga.base.utils.Locale.Companion.defaultLocale
import com.splendo.kaluga.base.utils.TimeZone
import com.splendo.kaluga.base.utils.enUsPosix

/**
 * Style used for formatting a [Date] to and from a [String]
 */
enum class DateFormatStyle {
    /**
     * Short style pattern
     */
    Short,

    /**
     * Medium style pattern
     */
    Medium,

    /**
     * Long style pattern
     */
    Long,

    /**
     * Full style pattern
     */
    Full
}

/**
 * Class for parsing and formatting a [Date] from/to a [String].
 */
expect class DateFormatter {

    companion object {
        /**
         * Creates a [DateFormatter] that only formats the date components of a [Date]
         * @param style The [DateFormatStyle] used for formatting the date components of the [Date]. Defaults to [DateFormatStyle.Medium].
         * @param timeZone The [TimeZone] for which the date should be formatted. Defaults to [TimeZone.current].
         * @param locale The [Locale] for which the date should be formatted. Defaults to [Locale.defaultLocale].
         */
        fun dateFormat(
            style: DateFormatStyle = DateFormatStyle.Medium,
            timeZone: TimeZone = TimeZone.current(),
            locale: Locale = defaultLocale
        ): DateFormatter

        /**
         * Creates a [DateFormatter] that only formats the time components of a [Date]
         * @param style The [DateFormatStyle] used for formatting the time components of the [Date]. Defaults to [DateFormatStyle.Medium].
         * @param timeZone The [TimeZone] for which the date should be formatted. Defaults to [TimeZone.current].
         * @param locale The [Locale] for which the date should be formatted. Defaults to [Locale.defaultLocale].
         */
        fun timeFormat(
            style: DateFormatStyle = DateFormatStyle.Medium,
            timeZone: TimeZone = TimeZone.current(),
            locale: Locale = defaultLocale
        ): DateFormatter

        /**
         * Creates a [DateFormatter] that formats both date and time components of a [Date]
         * @param dateStyle The [DateFormatStyle] used for formatting the date components of the [Date]. Defaults to [DateFormatStyle.Medium].
         * @param timeStyle The [DateFormatStyle] used for formatting the time components of the [Date]. Defaults to [DateFormatStyle.Medium].
         * @param timeZone The [TimeZone] for which the date should be formatted. Defaults to [TimeZone.current].
         * @param locale The [Locale] for which the date should be formatted. Defaults to [Locale.defaultLocale].
         */
        fun dateTimeFormat(
            dateStyle: DateFormatStyle = DateFormatStyle.Medium,
            timeStyle: DateFormatStyle = DateFormatStyle.Medium,
            timeZone: TimeZone = TimeZone.current(),
            locale: Locale = defaultLocale
        ): DateFormatter

        /**
         * Creates a [DateFormatter] using a custom Date format pattern.
         * On iOS some user settings may take precedent over the format (i.e. using 12 hour clock).
         * To prevent this, ensure that the provided [locale] is of a `POSIX` type.
         * A convenience [fixedPatternFormat] method exists to default to this behaviour.
         * @param pattern The pattern to apply.
         * @param timeZone The [TimeZone] for which the date should be formatted. Defaults to [TimeZone.current].
         * @param locale The [Locale] for which the date should be formatted. Defaults to [Locale.defaultLocale].
         */
        fun patternFormat(
            pattern: String,
            timeZone: TimeZone = TimeZone.current(),
            locale: Locale = defaultLocale
        ): DateFormatter
    }

    var pattern: String

    /**
     * The [TimeZone] this [DateFormatter] formats its dates to.
     */
    var timeZone: TimeZone

    /**
     * A list containing the names of all eras used by this date formatter.
     */
    var eras: List<String>

    /**
     * A list containing the names of all months used by this date formatter.
     */
    var months: List<String>

    /**
     * A list containing the shortened names of all months used by this date formatter.
     */
    var shortMonths: List<String>

    /**
     * A list containing the names of all weekdays used by this date formatter.
     */
    var weekdays: List<String>
    /**
     * A list containing the shortened names of all weekdays used by this date formatter.
     */
    var shortWeekdays: List<String>

    /**
     * The name used to describe A.M. when using a twelve hour clock.
     */
    var amString: String

    /**
     * The name used to describe P.M. when using a twelve hour clock.
     */
    var pmString: String

    /**
     * Formats a given [Date] to a [String] using the format described by this [DateFormatter].
     * @param date The [Date] to format.
     * @return The formatted [Date] as a [String]
     */
    fun format(date: Date): String

    /**
     * Attempts to parse a given [String] into a [Date] using the format described.
     * @param string The [String] to parse.
     * @return A [Date] matching the format described by [string] or `null` if no such match could be made.
     */
    fun parse(string: String): Date?
}

/**
 * Creates a fixed [DateFormatter] using a custom Date format pattern, localized by the [Locale.enUsPosix] [Locale].
 * Use this to ensure that displaying time in 12 or 24 hour format is not overridden by the user.
 * @param pattern The pattern to apply.
 * @param timeZone The [TimeZone] for which the date should be formatted. Defaults to [TimeZone.current].
 */
fun DateFormatter.Companion.fixedPatternFormat(pattern: String, timeZone: TimeZone = TimeZone.current()) = patternFormat(pattern, timeZone, Locale.enUsPosix)

/**
 * Creates a [DateFormatter] that formats time according to the ISo 8601 format.
 * @param timeZone The [TimeZone] for which the date should be formatted. Defaults to [TimeZone.current].
 */
fun DateFormatter.Companion.iso8601Pattern(timeZone: TimeZone = TimeZone.current()) = fixedPatternFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", timeZone)

/**
 * Creates a [DateFormatter] that only formats the date components of a [Date]
 * @param style The [DateFormatStyle] used for formatting the date components of the [Date]. Defaults to [DateFormatStyle.Medium].
 * @param excludeYear When [true] the year will not be part of the format.
 * @param timeZone The [TimeZone] for which the date should be formatted. Defaults to [TimeZone.current].
 * @param locale The [Locale] for which the date should be formatted. Defaults to [Locale.defaultLocale].
 */
fun DateFormatter.Companion.dateFormat(
    style: DateFormatStyle = DateFormatStyle.Medium,
    excludeYear: Boolean,
    timeZone: TimeZone = TimeZone.current(),
    locale: Locale = defaultLocale
): DateFormatter {
    val formatWithYear = dateFormat(style, timeZone, locale)
    return if (excludeYear) {
        patternFormat(patternWithoutYear(formatWithYear.pattern), timeZone, locale)
    } else {
        formatWithYear
    }
}

/**
 * Creates a [DateFormatter] that formats both date and time components of a [Date]
 * @param dateStyle The [DateFormatStyle] used for formatting the date components of the [Date]. Defaults to [DateFormatStyle.Medium].
 * @param excludeYear When [true] the year will not be part of the format.
 * @param timeStyle The [DateFormatStyle] used for formatting the time components of the [Date]. Defaults to [DateFormatStyle.Medium].
 * @param timeZone The [TimeZone] for which the date should be formatted. Defaults to [TimeZone.current].
 * @param locale The [Locale] for which the date should be formatted. Defaults to [Locale.defaultLocale].
 */
fun DateFormatter.Companion.dateTimeFormat(
    dateStyle: DateFormatStyle = DateFormatStyle.Medium,
    excludeYear: Boolean,
    timeStyle: DateFormatStyle = DateFormatStyle.Medium,
    timeZone: TimeZone = TimeZone.current(),
    locale: Locale = defaultLocale
): DateFormatter {
    val formatWithYear = dateTimeFormat(dateStyle, timeStyle, timeZone, locale)
    return if (excludeYear) {
        val datePatternWithYear = dateFormat(dateStyle, timeZone, locale).pattern
        val datePatternWithoutYear = patternWithoutYear(datePatternWithYear)
        patternFormat(formatWithYear.pattern.replace(datePatternWithYear, datePatternWithoutYear), timeZone, locale)
    } else {
        formatWithYear
    }
}

internal fun DateFormatter.Companion.patternWithoutYear(pattern: String): String = pattern.replace("\\W*[Yy]+\\W*".toRegex(), "")

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

internal enum class DateTime(val char: Char) {
    HOUR_OF_DAY_0('H'), // (00 - 23)
    HOUR_0('I'), // (01 - 12)
    HOUR_OF_DAY('k'), // (0 - 23) -- like H
    HOUR('l'), // (1 - 12) -- like I
    MINUTE('M'), // (00 - 59)
    NANOSECOND('N'), // (000000000 - 999999999)
    MILLISECOND('L'), // jdk, not in gnu (000 - 999)
    MILLISECOND_SINCE_EPOCH('Q'), // (0 - 99...?)
    AM_PM('p'), // (am or pm)
    SECONDS_SINCE_EPOCH('s'), // (0 - 99...?)
    SECOND('S'), // (00 - 60 - leap second)
    TIME('T'), // (24 hour hh:mm:ss)
    ZONE_NUMERIC('z'), // (-1200 - +1200) - ls minus?
    ZONE('Z'), // (symbol)

    // Date
    NAME_OF_DAY_ABBREV('a'), // 'a'
    NAME_OF_DAY('A'), // 'A'
    NAME_OF_MONTH_ABBREV('b'), // 'b'
    NAME_OF_MONTH('B'), // 'B'
    CENTURY('C'), // (00 - 99)
    DAY_OF_MONTH_0('d'), // (01 - 31)
    DAY_OF_MONTH('e'), // (1 - 31) -- like d

    NAME_OF_MONTH_ABBREV_X('h'), // -- same b
    DAY_OF_YEAR('j'), // (001 - 366)
    MONTH('m'), // (01 - 12)

    YEAR_2('y'), // (00 - 99)
    YEAR_4('Y'), // (0000 - 9999)

    // Composites
    TIME_12_HOUR('r'), // (hh:mm:ss [AP]M)
    TIME_24_HOUR('R'), // (hh:mm same as %H:%M)

    DATE_TIME('c'),

    // (Sat Nov 04 12:02:33 EST 1999)
    DATE('D'), // (mm/dd/yy)
    ISO_STANDARD_DATE('F'); // (%Y-%m-%d)

    companion object {
        internal fun parse(c: Char): DateTime {
            return DateTime.values().find { it.char == c } ?: throw StringFormatterException.UnknownFormatConversionException("t$c")
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Locale

internal class FixedString internal constructor(private val out: StringBuilder, private val s: String, private val start: Int, private val end: Int) :
    FormatString {
    override val index: Int = -2

    override fun print(arg: Any?, locale: Locale) {
        out.append(s, start, end)
    }

    override fun toString(): String {
        return s.substring(start, end)
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

enum class Flag(val char: Char) {
    LEFT_JUSTIFY('-'),
    UPPERCASE('^'),
    ALTERNATE('#'),

    // numerics
    PLUS('+'),
    LEADING_SPACE(' '),
    ZERO_PAD('0'),
    GROUP(','),
    PARENTHESES('('),

    // indexing
    PREVIOUS('<');

    companion object {
        fun parse(stringToMatch: String): Set<Flag> {
            val f = mutableSetOf<Flag>()
            for (c in stringToMatch) {
                val v = parse(c)
                if (f.contains(v)) throw StringFormatterException.DuplicateFormatFlagsException(v.toString())
                f.add(v)
            }
            return f
        }

        // parse those flags which may be provided by users
        private fun parse(c: Char): Flag {
            return values().find { it.char == c } ?: throw StringFormatterException.UnknownFormatFlagsException(c.toString())
        }
    }
}

fun Set<Flag>.toString(): String {
    val sb = StringBuilder()
    this.forEach {
        sb.append(it.char)
    }
    return sb.toString()
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.text.StringFormatter.Companion.getZero
import com.splendo.kaluga.base.utils.Date
import com.splendo.kaluga.base.utils.Locale
import com.splendo.kaluga.base.utils.TimeZone
import com.splendo.kaluga.base.utils.TimeZoneNameStyle
import com.splendo.kaluga.base.utils.toHexString
import kotlin.math.abs
import kotlin.math.absoluteValue

internal sealed class ParsingCharacter(val char: Char) {
    data class RegularCharacter(val regular: RegularFormatCharacter) : ParsingCharacter(regular.char)
    data class DateTime(val dateTime: com.splendo.kaluga.base.text.DateTime) : ParsingCharacter(dateTime.char)
    object None : ParsingCharacter(Char.MIN_VALUE)
}

internal class FormatSpecifier(private val out: StringBuilder, matchResult: MatchResult) : FormatString {

    companion object {
        val formatSpecifier = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z@%])".toRegex()
    }
    override var index: Int = -1
        private set
    private var flags: MutableSet<Flag> = mutableSetOf()
    private var width: Int = 0
    private var precision: Int = 0
    private var currentChar: ParsingCharacter = ParsingCharacter.None

    init {
        index(matchResult.groupValues[1])
        flags(matchResult.groupValues[2])
        width(matchResult.groupValues[3])
        precision(matchResult.groupValues[4])

        val timeStart = matchResult.groupValues[5]
        val parsingCharacter = matchResult.groupValues[6][0]
        if (timeStart.isNotEmpty()) {
            if (timeStart[0] == 'T')
                flags.add(Flag.UPPERCASE)
            val dateTime = ParsingCharacter.DateTime(DateTime.parse(parsingCharacter))
            checkDateTime(dateTime)
            currentChar = dateTime
        } else {
            val regularCharacter = regularConversion(parsingCharacter)
            when {
                regularCharacter.regular.isGeneral() -> checkGeneral(regularCharacter)
                regularCharacter.regular.isCharacter() -> checkCharacter(regularCharacter)
                regularCharacter.regular.isInteger() -> checkInteger(regularCharacter)
                regularCharacter.regular.isFloat() -> checkFloat(regularCharacter)
                regularCharacter.regular.isText() -> checkText(regularCharacter)
                else -> throw StringFormatterException.UnknownFormatConversionException(regularCharacter.char.toString())
            }
        }
    }

    override fun print(arg: Any?, locale: Locale) {
        when (val currentChar = currentChar) {
            is ParsingCharacter.DateTime -> printDateTime(arg, currentChar, locale)
            is ParsingCharacter.RegularCharacter -> {
                when (currentChar.regular) {
                    RegularFormatCharacter.DECIMAL_INTEGER,
                    RegularFormatCharacter.OCTAL_INTEGER,
                    RegularFormatCharacter.HEXADECIMAL_INTEGER -> printInteger(arg, currentChar, locale)
                    RegularFormatCharacter.SCIENTIFIC,
                    RegularFormatCharacter.GENERAL,
                    RegularFormatCharacter.DECIMAL_FLOAT,
                    RegularFormatCharacter.HEXADECIMAL_FLOAT -> printFloat(arg, currentChar, locale)
                    RegularFormatCharacter.CHARACTER,
                    RegularFormatCharacter.CHARACTER_UPPER -> printCharacter(arg, currentChar, locale)
                    RegularFormatCharacter.BOOLEAN -> printBoolean(arg, locale)
                    RegularFormatCharacter.STRING,
                    RegularFormatCharacter.STRING_IOS -> printString(arg, locale)
                    RegularFormatCharacter.HASHCODE -> printHashCode(arg, locale)
                    RegularFormatCharacter.LINE_SEPARATOR -> out.append(lineSeparator)
                    RegularFormatCharacter.PERCENT_SIGN -> print("%", locale)
                    else -> throw StringFormatterException.UnknownFormatConversionException(currentChar.char.toString())
                }
            }
            else -> throw StringFormatterException.UnknownFormatConversionException(currentChar.char.toString())
        }
    }

    private fun printDateTime(arg: Any?, currentChar: ParsingCharacter.DateTime, locale: Locale) {
        val date = when (arg) {
            null -> {
                print("null", locale)
                return
            }
            is Long,
            is Int,
            is Short -> {
                Date.epoch((arg as Number).toLong(), TimeZone.current(), locale)
            }
            is Date -> {
                arg.copy()
            }
            else -> throw StringFormatterException.IllegalFormatConversionException(currentChar.char, arg)
        }
        print(date, currentChar, locale)
    }

    private fun printInteger(arg: Any?, currentChar: ParsingCharacter.RegularCharacter, locale: Locale) {
        when (arg) {
            null -> print("null", locale)
            is Int -> print(arg, currentChar, locale)
            is Short -> print(arg, currentChar, locale)
            is Byte -> print(arg, currentChar, locale)
            is Long -> print(arg, currentChar, locale)
            else -> throw StringFormatterException.IllegalFormatConversionException(currentChar.char, arg)
        }
    }

    private fun printFloat(arg: Any?, currentChar: ParsingCharacter.RegularCharacter, locale: Locale) {
        when (arg) {
            null -> print("null", locale)
            is Float -> print(arg, currentChar, locale)
            is Double -> print(arg, currentChar, locale)
            else -> throw StringFormatterException.IllegalFormatConversionException(currentChar.char, arg)
        }
    }

    private fun printCharacter(arg: Any?, currentChar: ParsingCharacter.RegularCharacter, locale: Locale) {
        val stringToPrint = when (arg) {
            null -> "null"
            is Char -> arg.toString()
            is Byte -> arg.toInt().toChar().toString()
            is Short -> arg.toInt().toChar().toString()
            is Int -> arg.toChar().toString()
            else -> throw StringFormatterException.IllegalFormatConversionException(currentChar.char, arg)
        }
        print(stringToPrint, locale)
    }

    private fun printString(arg: Any?, locale: Locale) {
        (arg as? Formattable)?.let {
            out.append(it.formatFor(locale, flags, width, precision))
        } ?: run {
            if (flags.contains(Flag.ALTERNATE))
                throw StringFormatterException.FormatFlagsConversionMismatchException(Flag.ALTERNATE.toString(), 's')

            print(arg?.toString() ?: "null", locale)
        }
    }

    private fun printHashCode(arg: Any?, locale: Locale) {
        print(arg?.hashCode()?.toByte()?.let { byteArrayOf(it) }?.toHexString() ?: "null", locale)
    }

    private fun printBoolean(arg: Any?, locale: Locale) {
        print(
            (
                arg?.let {
                    (arg as? Boolean) ?: true
                } ?: false
                ).toString(),
            locale
        )
    }

    private fun print(s: String, locale: Locale) {
        val string = if (precision != -1 && precision < s.length) s.substring(0, precision) else s
        appendJustified(out, if (flags.contains(Flag.UPPERCASE)) string.upperCased(locale) else string)
    }

    private fun print(value: Byte, currentChar: ParsingCharacter.RegularCharacter, locale: Locale) {
        print(value.toLong(), currentChar, locale)
    }

    private fun print(value: Short, currentChar: ParsingCharacter.RegularCharacter, locale: Locale) {
        print(value.toLong(), currentChar, locale)
    }

    private fun print(value: Int, currentChar: ParsingCharacter.RegularCharacter, locale: Locale) {
        print(value.toLong(), currentChar, locale)
    }

    private fun print(value: Long, currentChar: ParsingCharacter.RegularCharacter, locale: Locale) {
        val sb = StringBuilder()
        when (currentChar.regular) {
            RegularFormatCharacter.DECIMAL_INTEGER -> {
                val valueStr = value.toString(10)
                val neg = value < 0
                // leading sign indicator
                leadingSign(sb, neg)

                // the value
                localizedMagnitude(sb, valueStr, if (neg) 1 else 0, flags, adjustWidth(width, flags, neg), locale)

                // trailing sign indicator
                trailingSign(sb, neg)
            }
            RegularFormatCharacter.OCTAL_INTEGER -> {
                checkBadFlags(currentChar, Flag.PARENTHESES, Flag.LEADING_SPACE, Flag.PLUS)
                val valueString = value.toString(8)

                val alternate = flags.contains(Flag.ALTERNATE)
                val length = valueString.length + if (alternate) 1 else 0
                if (alternate)
                    sb.append(getZero(locale))
                if (flags.contains(Flag.ZERO_PAD))
                    trailingZeros(sb, width - length)
                sb.append(valueString)
            }
            RegularFormatCharacter.HEXADECIMAL_INTEGER -> {
                checkBadFlags(currentChar, Flag.PARENTHESES, Flag.LEADING_SPACE, Flag.PLUS)
                val hexValue = value.toString(16)
                val alternate = flags.contains(Flag.ALTERNATE)
                val uppercase = flags.contains(Flag.UPPERCASE)
                val length = hexValue.length + if (alternate) 2 else 0
                if (alternate) {
                    val prefix = "${getZero(locale)}x"
                    sb.append(if (uppercase) prefix.upperCased(locale) else prefix)
                }
                if (flags.contains(Flag.ZERO_PAD))
                    trailingZeros(sb, width - length)
                sb.append(if (uppercase) hexValue.upperCased(locale) else hexValue)
            }
            else -> throw StringFormatterException.UnexpectedChar(currentChar.char)
        }

        // justify based on width
        appendJustified(out, sb)
    }

    private fun print(value: Float, currentChar: ParsingCharacter.RegularCharacter, locale: Locale) {
        print(value.toDouble(), currentChar, locale)
    }

    private fun print(value: Double, currentChar: ParsingCharacter.RegularCharacter, locale: Locale) {
        val sb = StringBuilder()
        val neg = value < 0.0
        val numberFormatter = NumberFormatter(locale)
        if (!value.isNaN()) {
            val v: Double = abs(value)

            // leading sign indicator
            leadingSign(sb, neg)

            // the value
            if (v.isFinite())
                print(sb, v, locale, currentChar, precision, neg)
            else {
                val infinitySymbol = numberFormatter.infinitySymbol
                sb.append(if (flags.contains(Flag.UPPERCASE)) infinitySymbol.upperCased(locale) else infinitySymbol)
            }

            // trailing sign indicator
            trailingSign(sb, neg)
        } else {
            val nanSymbol = numberFormatter.notANumberSymbol
            sb.append(if (flags.contains(Flag.UPPERCASE)) nanSymbol.upperCased(locale) else nanSymbol)
        }

        // justify based on width
        appendJustified(out, sb)
    }

    // !Double.isInfinite(value) && !Double.isNaN(value)
    private fun print(
        sb: StringBuilder,
        value: Double,
        locale: Locale,
        c: ParsingCharacter.RegularCharacter,
        precision: Int,
        neg: Boolean
    ) {
        when (c.regular) {
            RegularFormatCharacter.SCIENTIFIC -> {
                val prec = if (precision == -1) 6 else precision
                val number = StringBuilder()
                val formatter = NumberFormatter(locale, NumberFormatStyle.Scientific(minFractionDigits = prec.toUInt(), maxFractionDigits = prec.toUInt(), minExponent = 2U))
                val expSymbol = formatter.exponentSymbol
                val scientific = formatter.format(value).split(expSymbol, ignoreCase = true, limit = 2)
                val mantissa = scientific[0]
                val exponent = when {
                    value == 0.0 -> "+${formatter.zeroSymbol}${formatter.zeroSymbol}"
                    value.absoluteValue >= 1.0 -> "+${scientific[1]}"
                    else -> scientific[1]
                }
                number.append(mantissa)
                addZeros(number, prec)

                if (flags.contains(Flag.ALTERNATE) && prec == 0)
                    number.append(formatter.decimalSeparator)

                var newW = width
                if (width != -1) {
                    newW = adjustWidth(width - exponent.length - 1, flags, neg)
                }
                localizedMagnitude(sb, number, 0, flags, newW, locale)

                sb.append(if (flags.contains(Flag.UPPERCASE)) formatter.exponentSymbol.upperCased(locale) else formatter.exponentSymbol.lowerCased(locale))
                sb.append(exponent)
            }
            RegularFormatCharacter.DECIMAL_FLOAT -> {
                val prec = if (precision == -1) 6 else precision
                val number = StringBuilder()
                val formatter = NumberFormatter(locale, NumberFormatStyle.Decimal(minFractionDigits = prec.toUInt(), maxFractionDigits = prec.toUInt())).apply {
                    usesGroupingSeparator = false
                }
                if (value >= 0.0 && value < 1.0)
                    number.append(formatter.zeroSymbol)
                number.append(formatter.format(value))
                addZeros(number, prec)

                if (flags.contains(Flag.ALTERNATE) && prec == 0)
                    number.append(formatter.decimalSeparator)
                var newW = width
                if (width != -1) newW = adjustWidth(width, flags, neg)

                localizedMagnitude(sb, number, 0, flags, newW, locale)
            }
            RegularFormatCharacter.GENERAL -> {
                val scientificBuilder = StringBuilder()
                print(scientificBuilder, value, locale, ParsingCharacter.RegularCharacter(RegularFormatCharacter.SCIENTIFIC), precision, neg)
                val decimalBuilder = StringBuilder()
                print(decimalBuilder, value, locale, ParsingCharacter.RegularCharacter(RegularFormatCharacter.DECIMAL_FLOAT), precision, neg)
                val scientific = scientificBuilder.toString()
                val decimal = decimalBuilder.toString()
                if (decimal.length <= scientific.length)
                    sb.append(decimal)
                else
                    sb.append(scientific)
            }
            RegularFormatCharacter.HEXADECIMAL_FLOAT -> {
                // TODO Support Hexadecimal floats
            }
        }
    }

    private fun print(time: Date, currentChar: ParsingCharacter.DateTime, locale: Locale) {
        val sb = StringBuilder()
        print(sb, time, currentChar, locale)

        // justify based on width
        if (flags.contains(Flag.UPPERCASE)) {
            appendJustified(out, sb.toString().upperCased(locale))
        } else {
            appendJustified(out, sb)
        }
    }

    private fun print(sb: StringBuilder, time: Date, currentChar: ParsingCharacter.DateTime, locale: Locale): StringBuilder {
        when (currentChar.dateTime) {
            DateTime.HOUR_OF_DAY_0, DateTime.HOUR_0, DateTime.HOUR_OF_DAY, DateTime.HOUR -> {
                // 'l' (1 - 12) -- like I
                var i: Int = time.hour
                if (currentChar.dateTime == DateTime.HOUR_0 || currentChar.dateTime == DateTime.HOUR) i = if (i == 0 || i == 12) 12 else i % 12
                val flags = if (currentChar.dateTime == DateTime.HOUR_OF_DAY_0 || currentChar.dateTime == DateTime.HOUR_0)
                    setOf(Flag.ZERO_PAD)
                else
                    emptySet()
                sb.append(localizedMagnitude(value = i, flags = flags, width = 2, locale = locale))
            }
            DateTime.MINUTE -> {
                // 'M' (00 - 59)
                val i: Int = time.minute
                val flags = setOf(Flag.ZERO_PAD)
                sb.append(localizedMagnitude(value = i, flags = flags, width = 2, locale = locale))
            }
            DateTime.NANOSECOND -> {
                // 'N' (000000000 - 999999999)
                val i: Int = time.millisecond * 1000000
                val flags = setOf(Flag.ZERO_PAD)
                sb.append(localizedMagnitude(value = i, flags = flags, width = 9, locale = locale))
            }
            DateTime.MILLISECOND -> {
                // 'L' (000 - 999)
                val i: Int = time.millisecond
                val flags = setOf(Flag.ZERO_PAD)
                sb.append(localizedMagnitude(value = i, flags = flags, width = 3, locale = locale))
            }
            DateTime.MILLISECOND_SINCE_EPOCH -> {
                // 'Q' (0 - 99...?)
                val i: Long = time.millisecondSinceEpoch
                sb.append(localizedMagnitude(value = i.toString(10), offset = 0, flags = emptySet(), width = width, locale = locale))
            }
            DateTime.AM_PM -> {
                // 'p' (am or pm)
                val isAm = time.hour < 12
                val dateFormat = DateFormatter.patternFormat("aa", TimeZone.current(), locale)
                sb.append((if (isAm) dateFormat.amString else dateFormat.pmString).lowerCased(locale))
            }
            DateTime.SECONDS_SINCE_EPOCH -> {
                // 's' (0 - 99...?)
                val i: Long = time.millisecondSinceEpoch / 1000
                sb.append(localizedMagnitude(value = i.toString(10), offset = 0, flags = emptySet(), width = width, locale = locale))
            }
            DateTime.SECOND -> {
                // 'S' (00 - 60 - leap second)
                val i: Int = time.second
                val flags = setOf(Flag.ZERO_PAD)
                sb.append(localizedMagnitude(value = i, flags = flags, width = 2, locale = locale))
            }
            DateTime.ZONE_NUMERIC -> {
                // 'z' ({-|+}####) - ls minus?
                var i: Long = time.timeZone.offsetFromGMTAtDateInMilliseconds(time)
                val neg = i < 0
                sb.append(if (neg) '-' else '+')
                if (neg) i = -i
                val min = i / 60000

                // combine minute and hour into a single integer
                val offset = min / 60 * 100 + min % 60
                val flags = setOf(Flag.ZERO_PAD)
                sb.append(localizedMagnitude(value = offset.toString(10), offset = 0, flags = flags, width = 4, locale = locale))
            }
            DateTime.ZONE -> {
                // 'Z' (symbol)
                sb.append(time.timeZone.displayName(TimeZoneNameStyle.Short, time.timeZone.usesDaylightSavingsTime(time), locale))
            }
            DateTime.NAME_OF_DAY_ABBREV, DateTime.NAME_OF_DAY -> {
                // 'A'
                val i: Int = time.weekDay - 1
                val dateFormat = DateFormatter.patternFormat("EEEE")
                val weekdays = if (currentChar.dateTime == DateTime.NAME_OF_DAY) dateFormat.weekdays else dateFormat.shortWeekdays
                sb.append(weekdays[i])
            }
            DateTime.NAME_OF_MONTH_ABBREV, DateTime.NAME_OF_MONTH_ABBREV_X, DateTime.NAME_OF_MONTH -> {
                // 'B'
                val i: Int = time.month - 1
                val dateFormat = DateFormatter.patternFormat("MMMM")
                val months = if (currentChar.dateTime == DateTime.NAME_OF_MONTH) dateFormat.months else dateFormat.shortMonths
                sb.append(months[i])
            }
            DateTime.CENTURY, DateTime.YEAR_2, DateTime.YEAR_4 -> {
                // 'Y' (0000 - 9999)
                var i: Int = time.year
                var size = 2
                when (currentChar.dateTime) {
                    DateTime.CENTURY -> i /= 100
                    DateTime.YEAR_2 -> i %= 100
                    DateTime.YEAR_4 -> size = 4
                }
                val flags = setOf(Flag.ZERO_PAD)
                sb.append(localizedMagnitude(value = i, flags = flags, width = size, locale = locale))
            }
            DateTime.DAY_OF_MONTH_0, DateTime.DAY_OF_MONTH -> {
                // 'e' (1 - 31) -- like d
                val i: Int = time.day
                val flags = if (currentChar.dateTime == DateTime.DAY_OF_MONTH_0) setOf(Flag.ZERO_PAD) else emptySet()
                sb.append(localizedMagnitude(value = i, flags = flags, width = 2, locale = locale))
            }
            DateTime.DAY_OF_YEAR -> {
                // 'j' (001 - 366)
                val i: Int = time.dayOfYear
                val flags = setOf(Flag.ZERO_PAD)
                sb.append(localizedMagnitude(value = i, flags = flags, width = 3, locale = locale))
            }
            DateTime.MONTH -> {
                // 'm' (01 - 12)
                val i: Int = time.month
                val flags = setOf(Flag.ZERO_PAD)
                sb.append(localizedMagnitude(value = i, flags = flags, width = 2, locale = locale))
            }
            DateTime.TIME, DateTime.TIME_24_HOUR -> {
                // 'R' (hh:mm same as %H:%M)
                val sep = ':'
                print(sb, time, ParsingCharacter.DateTime(DateTime.HOUR_OF_DAY_0), locale).append(sep)
                print(sb, time, ParsingCharacter.DateTime(DateTime.MINUTE), locale)
                if (currentChar.dateTime == DateTime.TIME) {
                    sb.append(sep)
                    print(sb, time, ParsingCharacter.DateTime(DateTime.SECOND), locale)
                }
            }
            DateTime.TIME_12_HOUR -> {
                // 'r' (hh:mm:ss [AP]M)
                val sep = ':'
                print(sb, time, ParsingCharacter.DateTime(DateTime.HOUR_0), locale).append(sep)
                print(sb, time, ParsingCharacter.DateTime(DateTime.MINUTE), locale).append(sep)
                print(sb, time, ParsingCharacter.DateTime(DateTime.SECOND), locale).append(' ')

                // this may be in wrong place for some locales
                val tsb = StringBuilder()
                print(tsb, time, ParsingCharacter.DateTime(DateTime.AM_PM), locale)
                sb.append(tsb.toString().upperCased(locale))
            }
            DateTime.DATE_TIME -> {
                // 'c' (Sat Nov 04 12:02:33 EST 1999)
                val sep = ' '
                print(sb, time, ParsingCharacter.DateTime(DateTime.NAME_OF_DAY_ABBREV), locale).append(sep)
                print(sb, time, ParsingCharacter.DateTime(DateTime.NAME_OF_MONTH_ABBREV), locale).append(sep)
                print(sb, time, ParsingCharacter.DateTime(DateTime.DAY_OF_MONTH_0), locale).append(sep)
                print(sb, time, ParsingCharacter.DateTime(DateTime.TIME), locale).append(sep)
                print(sb, time, ParsingCharacter.DateTime(DateTime.ZONE), locale).append(sep)
                print(sb, time, ParsingCharacter.DateTime(DateTime.YEAR_4), locale)
            }
            DateTime.DATE -> {
                // 'D' (mm/dd/yy)
                val sep = '/'
                print(sb, time, ParsingCharacter.DateTime(DateTime.MONTH), locale).append(sep)
                print(sb, time, ParsingCharacter.DateTime(DateTime.DAY_OF_MONTH_0), locale).append(sep)
                print(sb, time, ParsingCharacter.DateTime(DateTime.YEAR_2), locale)
            }
            DateTime.ISO_STANDARD_DATE -> {
                // 'F' (%Y-%m-%d)
                val sep = '-'
                print(sb, time, ParsingCharacter.DateTime(DateTime.YEAR_4), locale).append(sep)
                print(sb, time, ParsingCharacter.DateTime(DateTime.MONTH), locale).append(sep)
                print(sb, time, ParsingCharacter.DateTime(DateTime.DAY_OF_MONTH_0), locale)
            }
        }
        return sb
    }

    private fun addZeros(sb: StringBuilder, prec: Int) {

        // Look for the dot.  If we don't find one, the we'll need to add

        // it before we add the zeros.
        val len: Int = sb.length
        var i = 0
        while (i < len) {
            if (sb[i] == '.') {
                break
            }
            i++
        }
        var needDot = false
        if (i == len) {
            needDot = true
        }

        // Determine existing precision.
        val outPrec = len - i - if (needDot) 0 else 1
        if (outPrec == prec) {
            return
        }

        // Add dot if previously determined to be necessary.
        if (needDot) {
            sb.append('.')
        }

        // Add zeros.
        trailingZeros(sb, prec - outPrec)
    }

    private fun appendJustified(out: StringBuilder, cs: CharSequence): StringBuilder {
        if (width == -1) {
            return out.append(cs)
        }
        val padRight: Boolean = flags.contains(Flag.LEFT_JUSTIFY)
        val sp = width - cs.length
        if (padRight) {
            out.append(cs)
        }
        for (i in 0 until sp) {
            out.append(' ')
        }
        if (!padRight) {
            out.append(cs)
        }
        return out
    }

    // neg := val < 0
    private fun leadingSign(sb: StringBuilder, neg: Boolean): StringBuilder {
        if (!neg) {
            if (flags.contains(Flag.PLUS)) {
                sb.append('+')
            } else if (flags.contains(Flag.LEADING_SPACE)) {
                sb.append(' ')
            }
        } else {
            if (flags.contains(Flag.PARENTHESES)) sb.append('(') else sb.append('-')
        }
        return sb
    }

    // neg := val < 0
    private fun trailingSign(sb: StringBuilder, neg: Boolean): StringBuilder {
        if (neg && flags.contains(Flag.PARENTHESES)) sb.append(')')
        return sb
    }

    private fun localizedMagnitude(
        sb: StringBuilder = StringBuilder(),
        value: Int,
        flags: Set<Flag>,
        width: Int,
        locale: Locale
    ): StringBuilder {
        return localizedMagnitude(sb, value.toString(10), 0, flags, width, locale)
    }

    private fun localizedMagnitude(
        sb: StringBuilder = StringBuilder(),
        value: CharSequence,
        offset: Int,
        flags: Set<Flag>,
        width: Int,
        locale: Locale
    ): StringBuilder {
        val begin: Int = sb.length
        val zero: Char = getZero(locale)

        // determine localized grouping separator and size
        var grpSep = '\u0000'
        var grpSize = -1
        var decSep = '\u0000'
        val len = value.length
        var dot = len
        for (j in offset until len) {
            if (value[j] == '.') {
                dot = j
                break
            }
        }
        val numberFormatter = NumberFormatter(locale)
        if (dot < len) {
            decSep = numberFormatter.decimalSeparator
        }
        if (flags.contains(Flag.GROUP)) {
            grpSep = numberFormatter.groupingSeparator
            grpSize = numberFormatter.groupingSize

            // Some locales do not use grouping (the number
            // pattern for these locales does not contain group, e.g.
            // ("#0.###")), but specify a grouping separator.
            // To avoid unnecessary identification of the position of
            // grouping separator, reset its value with null character
            if (!numberFormatter.usesGroupingSeparator || grpSize == 0) {
                grpSep = '\u0000'
            }
        }

        // localize the digits inserting group separators as necessary
        for (j in offset until len) {
            if (j == dot) {
                sb.append(decSep)

                // no more group separators after the decimal separator
                grpSep = '\u0000'
                continue
            }
            val c = value[j]
            sb.append((c - '0' + zero.code).toChar())
            if (grpSep != '\u0000' && j != dot - 1 && (dot - j) % grpSize == 1) {
                sb.append(grpSep)
            }
        }

        // apply zero padding
        if (width != -1 && flags.contains(Flag.ZERO_PAD)) {
            for (k in sb.length until width) {
                sb.insert(begin, zero)
            }
        }
        return sb
    }

    private fun adjustWidth(width: Int, flags: Set<Flag>, neg: Boolean): Int {
        var newW = width
        if (newW != -1 && neg && flags.contains(Flag.PARENTHESES)) newW--
        return newW
    }

    // Add trailing zeros

    // Add trailing zeros
    private fun trailingZeros(sb: StringBuilder, nzeros: Int) {
        for (i in 0 until nzeros) {
            sb.append('0')
        }
    }

    private fun index(stringToMatch: String): Int {
        index = if (stringToMatch.isNotEmpty()) {
            try {
                // skip the trailing '$'
                stringToMatch.substring(0, stringToMatch.length - 1).toInt(10)
            } catch (x: NumberFormatException) {
                throw StringFormatterException.NumberFormatException(x)
            }
        } else {
            0
        }
        return index
    }

    private fun flags(stringToMatch: String): Set<Flag> {
        flags = Flag.parse(stringToMatch).toMutableSet()
        if (flags.contains(Flag.PREVIOUS)) index = -1
        return flags
    }

    private fun checkBadFlags(currentChar: ParsingCharacter, vararg badFlags: Flag) {
        for (badFlag in badFlags)
            if (flags.contains(badFlag))
                throw StringFormatterException.FormatFlagsConversionMismatchException(badFlag.toString(), currentChar.char)
    }

    private fun width(stringToMatch: String): Int {
        width = -1
        if (stringToMatch.isNotEmpty()) {
            try {
                width = stringToMatch.toInt(10)
                if (width < 0) throw StringFormatterException.IllegalFormatWidthException(width)
            } catch (x: NumberFormatException) {
                throw StringFormatterException.NumberFormatException(x)
            }
        }
        return width
    }

    private fun precision(stringToMatch: String): Int {
        precision = -1
        if (stringToMatch.isNotEmpty()) {
            try {
                // skip the leading '.'
                precision = stringToMatch.substring(1, stringToMatch.length).toInt(10)
                if (precision < 0) throw StringFormatterException.IllegalFormatPrecisionException(precision)
            } catch (x: NumberFormatException) {
                throw StringFormatterException.NumberFormatException(x)
            }
        }
        return precision
    }

    private fun regularConversion(conv: Char): ParsingCharacter.RegularCharacter {
        var currentRegularChar = RegularFormatCharacter.parse(conv)
        if (conv.uppercaseChar() == conv && conv.lowercaseChar() != conv) {
            flags.add(Flag.UPPERCASE)
            currentRegularChar = RegularFormatCharacter.parse(conv.lowercaseChar())
        }
        if (currentRegularChar.isText()) {
            index = -2
        }
        return ParsingCharacter.RegularCharacter(currentRegularChar).also {
            currentChar = it
        }
    }

    private fun checkGeneral(currentChar: ParsingCharacter.RegularCharacter) {
        if ((currentChar.regular == RegularFormatCharacter.BOOLEAN || currentChar.regular == RegularFormatCharacter.HASHCODE) && flags.contains(Flag.ALTERNATE))
            throw StringFormatterException.FormatFlagsConversionMismatchException(Flag.ALTERNATE.toString(), currentChar.char)

        // '-' requires a width
        if (width == -1 && flags.contains(Flag.LEFT_JUSTIFY)) throw StringFormatterException.MissingFormatWidthException(toString())
        checkBadFlags(
            currentChar,
            Flag.PLUS,
            Flag.LEADING_SPACE,
            Flag.ZERO_PAD,
            Flag.GROUP,
            Flag.PARENTHESES
        )
    }

    private fun checkDateTime(currentChar: ParsingCharacter.DateTime) {
        if (precision != -1) throw StringFormatterException.IllegalFormatPrecisionException(precision)
        checkBadFlags(
            currentChar,
            Flag.ALTERNATE,
            Flag.PLUS,
            Flag.LEADING_SPACE,
            Flag.ZERO_PAD,
            Flag.GROUP,
            Flag.PARENTHESES
        )

        // '-' requires a width
        if (width == -1 && flags.contains(Flag.LEFT_JUSTIFY)) throw StringFormatterException.MissingFormatWidthException(toString())
    }

    private fun checkCharacter(currentChar: ParsingCharacter.RegularCharacter) {
        if (precision != -1) throw StringFormatterException.IllegalFormatPrecisionException(precision)
        checkBadFlags(
            currentChar,
            Flag.ALTERNATE,
            Flag.PLUS,
            Flag.LEADING_SPACE,
            Flag.ZERO_PAD,
            Flag.GROUP,
            Flag.PARENTHESES
        )

        // '-' requires a width
        if (width == -1 && flags.contains(Flag.LEFT_JUSTIFY)) throw StringFormatterException.MissingFormatWidthException(toString())
    }

    private fun checkInteger(currentChar: ParsingCharacter.RegularCharacter) {
        checkNumeric()
        if (precision != -1) throw StringFormatterException.IllegalFormatPrecisionException(precision)
        if (currentChar.regular == RegularFormatCharacter.DECIMAL_INTEGER) checkBadFlags(currentChar, Flag.ALTERNATE) else if (currentChar.regular == RegularFormatCharacter.OCTAL_INTEGER) checkBadFlags(currentChar, Flag.GROUP) else checkBadFlags(currentChar, Flag.GROUP)
    }

    private fun checkFloat(currentChar: ParsingCharacter.RegularCharacter) {
        checkNumeric()
        when (currentChar.regular) {
            RegularFormatCharacter.DECIMAL_FLOAT -> {}
            RegularFormatCharacter.HEXADECIMAL_FLOAT -> {
                checkBadFlags(currentChar, Flag.PARENTHESES, Flag.GROUP)
            }
            RegularFormatCharacter.SCIENTIFIC -> {
                checkBadFlags(currentChar, Flag.GROUP)
            }
            RegularFormatCharacter.GENERAL -> {
                checkBadFlags(currentChar, Flag.ALTERNATE)
            }
        }
    }

    private fun checkNumeric() {
        if (width != -1 && width < 0) throw StringFormatterException.IllegalFormatWidthException(width)
        if (precision != -1 && precision < 0) throw StringFormatterException.IllegalFormatPrecisionException(precision)

        // '-' and '0' require a width
        if (width == -1 && (flags.contains(Flag.LEFT_JUSTIFY) || flags.contains(Flag.ZERO_PAD)))
            throw StringFormatterException.MissingFormatWidthException(toString())

        // bad combination
        if (flags.contains(Flag.PLUS) && flags.contains(Flag.LEADING_SPACE) || flags.contains(Flag.LEFT_JUSTIFY) && flags.contains(Flag.ZERO_PAD))
            throw StringFormatterException.IllegalFormatFlagsException(flags.toString())
    }

    private fun checkText(currentChar: ParsingCharacter.RegularCharacter) {
        if (precision != -1) throw StringFormatterException.IllegalFormatPrecisionException(precision)
        when (currentChar.regular) {
            RegularFormatCharacter.PERCENT_SIGN -> {
                when (flags.size) {
                    0 -> {}
                    1 -> {
                        if (!flags.contains(Flag.LEFT_JUSTIFY)) {
                            throw StringFormatterException.IllegalFormatFlagsException(flags.toString())
                        } else if (width == -1)
                            throw StringFormatterException.MissingFormatWidthException(toString())
                    }
                    else -> throw StringFormatterException.IllegalFormatFlagsException(flags.toString())
                }
            }
            RegularFormatCharacter.LINE_SEPARATOR -> {
                if (width != -1) throw StringFormatterException.IllegalFormatWidthException(width)
                if (flags.isNotEmpty()) throw StringFormatterException.IllegalFormatFlagsException(flags.toString())
            }
            else -> throw StringFormatterException.UnexpectedChar(currentChar.char)
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Locale

internal interface FormatString {
    val index: Int
    fun print(arg: Any?, locale: Locale)
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Locale
import com.splendo.kaluga.base.utils.Locale.Companion.defaultLocale
import kotlin.math.max

/**
 * Rounding mode applied when rounding aa given [Number] while formatting
 */
enum class RoundingMode {
    /**
     * Round towards positive infinity.
     */
    Ceiling,

    /**
     * Round towards negative infinity.
     */
    Floor,

    /**
     * Round towards zero.
     */
    Down,

    /**
     * Round away from zero.
     */
    Up,

    /**
     * Round towards the nearest integer, or towards an even number if equidistant.
     */
    HalfEven,

    /**
     * Round towards the nearest integer, or towards zero if equidistant.
     */
    HalfDown,

    /**
     * Round towards the nearest integer, or away from zero if equidistant.
     */
    HalfUp
}

@ExperimentalUnsignedTypes
/**
 * Style used for formatting a [Number] to and from a [String]
 */
sealed class NumberFormatStyle(open val roundingMode: RoundingMode) {

    /**
     * Formats a number to an integer representation
     * @param minDigits The minimum number of digits to show. Defaults to `0U`.
     * @param maxDigits The maximum number of digits to show. Defaults to `309U`.
     * @param roundingMode The [RoundingMode] to be applied. Defaults to [RoundingMode.HalfEven].
     */
    data class Integer(
        val minDigits: UInt = 0U,
        val maxDigits: UInt = 309U,
        override val roundingMode: RoundingMode = RoundingMode.HalfEven
    ) : NumberFormatStyle(roundingMode)

    /**
     * Formats a number to a decimal representation
     * @param minIntegerDigits The minimum number of integer digits to show. Defaults to `0U`.
     * @param maxIntegerDigits The maximum number of integer digits to show. Defaults to `309U`.
     * @param minFractionDigits The minimum number of decimal digits to show. Defaults to `0U`.
     * @param maxFractionDigits The maximum number of decimal digits to show. Defaults to `325U`.
     * @param roundingMode The [RoundingMode] to be applied. Defaults to [RoundingMode.HalfEven].
     */
    data class Decimal(
        val minIntegerDigits: UInt = 0U,
        val maxIntegerDigits: UInt = 309U,
        val minFractionDigits: UInt = 0U,
        val maxFractionDigits: UInt = 325U,
        override val roundingMode: RoundingMode = RoundingMode.HalfEven
    ) : NumberFormatStyle(roundingMode)

    /**
     * Formats a number to a percentage value.
     * 100% is represented by 1.0, so 0.8 will be formatted as 80%.
     * @param minIntegerDigits The minimum number of integer digits to show. Defaults to `0U`.
     * @param maxIntegerDigits The maximum number of integer digits to show. Defaults to `309U`.
     * @param minFractionDigits The minimum number of decimal digits to show. Defaults to `0U`.
     * @param maxFractionDigits The maximum number of decimal digits to show. Defaults to `325U`.
     * @param roundingMode The [RoundingMode] to be applied. Defaults to [RoundingMode.HalfEven].
     */
    data class Percentage(
        val minIntegerDigits: UInt = 0U,
        val maxIntegerDigits: UInt = 309U,
        val minFractionDigits: UInt = 0U,
        val maxFractionDigits: UInt = 325U,
        override val roundingMode: RoundingMode = RoundingMode.HalfEven
    ) : NumberFormatStyle(roundingMode)

    /**
     * Formats a number to a permillage value.
     * 1000‰ is represented by 1.0, so 0.8 will be formatted as 800‰.
     * @param minIntegerDigits The minimum number of integer digits to show. Defaults to `0U`.
     * @param maxIntegerDigits The maximum number of integer digits to show. Defaults to `309U`.
     * @param minFractionDigits The minimum number of decimal digits to show. Defaults to `0U`.
     * @param maxFractionDigits The maximum number of decimal digits to show. Defaults to  `325U`.
     * @param roundingMode The [RoundingMode] to be applied. Defaults to [RoundingMode.HalfEven].
     */
    data class Permillage(
        val minIntegerDigits: UInt = 0U,
        val maxIntegerDigits: UInt = 309U,
        val minFractionDigits: UInt = 0U,
        val maxFractionDigits: UInt = 325U,
        override val roundingMode: RoundingMode = RoundingMode.HalfEven
    ) : NumberFormatStyle(roundingMode)

    /**
     * Formats a number to its scientific notation.
     * @param minIntegerDigits The minimum number of integer digits to show in the mantissa. Defaults to `1U`.
     * @param maxIntegerDigits The maximum number of integer digits to show in the mantissa.
     * If this is greater than [minIntegerDigits], it forces the exponent to be a multiple of maximum number of integer values.
     * Otherwise the minimum number of integer digits is achieved by adjusting the exponent.
     * Defaults to [minIntegerDigits]
     * @param minFractionDigits The minimum number of decimal digits to show in the mantissa. Defaults to `1U`.
     * @param maxFractionDigits The maximum number of decimal digits to show in the mantissa. Defaults to `16U`.
     * @param minExponent The minimum number of digits to show in the exponent. Defaults to `1U`.
     * @param roundingMode The [RoundingMode] to be applied. Defaults to [RoundingMode.HalfEven].
     */
    data class Scientific(
        val minIntegerDigits: UInt = 1U,
        val maxIntegerDigits: UInt = minIntegerDigits,
        val minFractionDigits: UInt = 1U,
        val maxFractionDigits: UInt = 16U,
        val minExponent: UInt = 1U,
        override val roundingMode: RoundingMode = RoundingMode.HalfEven
    ) : NumberFormatStyle(roundingMode) {
        val pattern: String get() {
            val mantissaInteger = "${"#".repeat(max(maxIntegerDigits.toInt() - minIntegerDigits.toInt(), 0))}${"0".repeat(max(minIntegerDigits.toInt(), 1))}"
            val mantissaDecimal = "${"0".repeat(minFractionDigits.toInt())}${"#".repeat(max(maxFractionDigits.toInt() - minFractionDigits.toInt(), 0))}"
            val mantissa = if (mantissaDecimal.isNotEmpty()) "$mantissaInteger.$mantissaDecimal" else mantissaInteger
            val exponent = "E${"0".repeat(if (minExponent > 0U) minExponent.toInt() else 0)}"
            return "$mantissa$exponent"
        }
    }

    /**
     * Formats a number to a currency of a given currency code fitting its locale.
     * Note: Some platforms may use different delimiters between the currency symbol and the number (if present for the locale).
     * For instance, iOS often uses a non-breaking space, whereas Android uses a common space.
     * This inconsistency is not corrected by this library and should thus be manually corrected if required.
     * @param currencyCode The currency code to format for. When `null` defaults to the currency of the locale Defaults to `null`.
     * @param minIntegerDigits The minimum number of integer digits to show. Defaults to `0U`.
     * @param maxIntegerDigits The maximum number of integer digits to show. Defaults to `309U`.
     * @param minFractionDigits The minimum number of decimal digits to show. Defaults to `0U`.
     * @param maxFractionDigits The maximum number of decimal digits to show. When `null` uses the preferred faction digits of the currency. Defaults to `null`.
     * @param roundingMode The [RoundingMode] to be applied. Defaults to [RoundingMode.HalfEven].
     */
    data class Currency(
        val currencyCode: String? = null,
        val minIntegerDigits: UInt = 0U,
        val maxIntegerDigits: UInt = 309U,
        val minFractionDigits: UInt? = null,
        val maxFractionDigits: UInt? = null,
        override val roundingMode: RoundingMode = RoundingMode.HalfEven
    ) : NumberFormatStyle(roundingMode)

    /**
     * Formats a number to a pattern.
     * @param positivePattern The pattern to apply for positive numbers.
     * @param negativePattern The pattern to apply for negative numbers. Defaults to "-[positivePattern]".
     * @param roundingMode The [RoundingMode] to be applied. Defaults to [RoundingMode.HalfEven].
     */
    data class Pattern(val positivePattern: String, val negativePattern: String = "-$positivePattern", override val roundingMode: RoundingMode = RoundingMode.HalfEven) : NumberFormatStyle(roundingMode)
}

/**
 * Class for parsing and formatting a [Number] from/to a [String].
 * @param locale The [Locale] used for parsing. Defaults to [Locale.defaultLocale].
 * @param style The [NumberFormatStyle] to configure the format to use. Defaults to [NumberFormatStyle.Decimal].
 */
expect class NumberFormatter constructor(locale: Locale = defaultLocale, style: NumberFormatStyle = NumberFormatStyle.Decimal()) {
    /**
     * [Locale] used for formatting.
     */
    val locale: Locale

    /**
     * The symbol used to represent a percent sign.
     */
    var percentSymbol: Char

    /**
     * The symbol used to represent a permille sign.
     */
    var perMillSymbol: Char

    /**
     * The symbol used to represent a minus sign.
     */
    var minusSign: Char

    /**
     * The symbol used to represent an exponent sign.
     */
    var exponentSymbol: String

    /**
     * The symbol used to represent a zero sign.
     */
    var zeroSymbol: Char

    /**
     * The text used to represent a `Not a Number` value.
     */
    var notANumberSymbol: String

    /**
     * The text used to represent infinity.
     */
    var infinitySymbol: String

    /**
     * The text used to represent the currency for the current locale.
     */
    var currencySymbol: String

    /**
     * The ISO 4217 currency code of this currency
     */
    var currencyCode: String

    /**
     * Text to be added in front of the number when positive
     */
    var positivePrefix: String
    /**
     * Text to be added behind the number when positive
     */
    var positiveSuffix: String
    /**
     * Text to be added in front of the number when negative
     */
    var negativePrefix: String
    /**
     * Text to be added behind the number when negative
     */
    var negativeSuffix: String

    /**
     * The symbol used to represent a grouping sign.
     */
    var groupingSeparator: Char

    /**
     * When set to `true` number grouping is applied
     */
    var usesGroupingSeparator: Boolean

    /**
     * The symbol used to represent a decimal separator.
     */
    var decimalSeparator: Char

    /**
     * When set to `true` always shows a decimal separator, even when no decimal will be shown.
     */
    var alwaysShowsDecimalSeparator: Boolean

    /**
     * The symbol used to represent a decimal separator when formatting currency.
     */
    var currencyDecimalSeparator: Char

    /**
     * The number of digits that will be grouped by [groupingSeparator].
     */
    var groupingSize: Int

    /**
     * The value a number will be multiplied with when formatted.
     */
    var multiplier: Int

    /**
     * Formats a [Number] into a [String].
     * @param number The [Number] to format.
     * @return The [String] representation of [number] according to this format.
     */
    fun format(number: Number): String

    /**
     * Tries to parse a [String] into a [Number]
     * @param string The [String] to parse.
     * @return A [Number] if the String could by parsed by this formatter, `null` otherwise.
     */
    fun parse(string: String): Number?
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

internal enum class RegularFormatCharacter(val char: Char) {
    // Byte, Short, Integer, Long
    DECIMAL_INTEGER('d'),
    OCTAL_INTEGER('o'),
    HEXADECIMAL_INTEGER('x'),
    HEXADECIMAL_INTEGER_UPPER('X'),

    // Float, Double
    SCIENTIFIC('e'),
    SCIENTIFIC_UPPER('E'),
    GENERAL('g'),
    GENERAL_UPPER('G'),
    DECIMAL_FLOAT('f'),
    HEXADECIMAL_FLOAT('a'),
    HEXADECIMAL_FLOAT_UPPER('A'),

    // Character, Byte, Short, Integer
    CHARACTER('c'),
    CHARACTER_UPPER('C'),

    // Calendar, long
    DATE_TIME('t'),
    DATE_TIME_UPPER('T'),

    // if (arg.TYPE != boolean) return boolean
    // if (arg != null) return true; else return false;
    BOOLEAN('b'),
    BOOLEAN_UPPER('B'),

    STRING('s'),
    STRING_IOS('@'),
    STRING_UPPER('S'),

    // arg.hashCode()
    HASHCODE('h'),
    HASHCODE_UPPER('H'),
    LINE_SEPARATOR('n'),
    PERCENT_SIGN('%');

    companion object {
        internal fun parse(c: Char): RegularFormatCharacter {
            return RegularFormatCharacter.values().find { it.char == c } ?: throw throw StringFormatterException.UnknownFormatConversionException(c.toString())
        }
    }

    // Returns true if and only if the Conversion is applicable to all objects.
    fun isGeneral(): Boolean {
        return when (this) {
            BOOLEAN, BOOLEAN_UPPER, STRING, STRING_UPPER, STRING_IOS, HASHCODE, HASHCODE_UPPER -> true
            else -> false
        }
    }

    // Returns true if and only if the Conversion is applicable to character.
    fun isCharacter(): Boolean {
        return when (this) {
            CHARACTER, CHARACTER_UPPER -> true
            else -> false
        }
    }

    // Returns true if and only if the Conversion is an integer type.
    fun isInteger(): Boolean {
        return when (this) {
            DECIMAL_INTEGER, OCTAL_INTEGER, HEXADECIMAL_INTEGER, HEXADECIMAL_INTEGER_UPPER -> true
            else -> false
        }
    }

    // Returns true if and only if the Conversion is a floating-point type.
    fun isFloat(): Boolean {
        return when (this) {
            SCIENTIFIC, SCIENTIFIC_UPPER, GENERAL, GENERAL_UPPER, DECIMAL_FLOAT, HEXADECIMAL_FLOAT, HEXADECIMAL_FLOAT_UPPER -> true
            else -> false
        }
    }

    // Returns true if and only if the Conversion does not require an argument
    fun isText(): Boolean {
        return when (this) {
            LINE_SEPARATOR, PERCENT_SIGN -> true
            else -> false
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.text.FormatSpecifier.Companion.formatSpecifier
import com.splendo.kaluga.base.utils.Locale
import com.splendo.kaluga.base.utils.Locale.Companion.defaultLocale

/**
 * Formats this [String] as a printf-style format String using a variable number of arguments.
 * @param args The list of arguments used for formatting the string.
 * @param locale The [Locale] used for formatting the arguments. This is relevant for number and date formatting, as well as capitalization.
 * @return The formatted [String]
 * @throws [StringFormatterException] if the string could not be formatted using [args].
 */
fun String.format(vararg args: Any?, locale: Locale = defaultLocale) = StringFormatter(locale = locale).format(this, *args).toString()

/**
 * Interface that adds support for an object to be formatted by [StringFormatter].
 */
interface Formattable {
    /**
     * Formats this object to a [String] using a [Locale], [Flags], width, and precision
     * @param locale The [Locale] used for formatting.
     * @param flags A [Set] of [Flag] used for formattting.
     * @param width The expected width of the result.
     * @param precision The precision applied when formatting.
     * @return The formatted [String] representing this [Formattable].
     * @throws [StringFormatterException] when an issue with the formatting occurs.
     */
    fun formatFor(locale: Locale, flags: Set<Flag>, width: Int, precision: Int): String
}

/**
 * Formats a given [String] using a printf-style format strings.
 * Supports formats for [Number], [String], [Char], [Boolean], and [com.splendo.kaluga.base.utils.Date].
 * Custom formatting is supported by implementing [Formattable].
 * Flags, precision and width are supported by this formatter as well.
 * Formatting will adjust for a provided [Locale].
 * May throw an [StringFormatterException] if the incorrect format is applied.
 * @param out he [StringBuilder] used for outputting the result.
 * @param locale The [Locale] used for formatting. This is relevant for number and date formatting, as well as capitalization.
 */
class StringFormatter(private val out: StringBuilder = StringBuilder(), private val locale: Locale = defaultLocale) {

    companion object {
        internal fun getZero(locale: Locale): Char = NumberFormatter(locale).zeroSymbol
        internal fun checkText(s: String, start: Int, end: Int) {
            val index = s.substring(start, end).indexOf('%')
            if (index >= 0) {
                throw StringFormatterException.UnknownFormatConversionException((if (index == end - 1) '%' else s[index + 1]).toString())
            }
        }
    }

    /**
     * Formats a [String] using a printf-style format String and a variable number of arguments.
     * @param format The printf-style format [String]
     * @param args The list of arguments used for formatting the string.
     * @return The [StringBuilder] containing the formatted [String].
     * @throws [StringFormatterException] if [format] could not be formatted using [args].
     */
    fun format(format: String, vararg args: Any?): StringBuilder {
        format(locale, format, *args)
        return out
    }

    private fun format(locale: Locale, format: String, vararg args: Any?): StringFormatter {
        var last = -1
        var lastOrdinaryIndex = -1
        val formatStrings = parse(format)
        formatStrings.forEach { formatString ->
            when (val index = formatString.index) {
                -2 -> formatString.print(null, locale)
                -1 -> {
                    if (last < 0 || last > args.size - 1) {
                        throw StringFormatterException.MissingFormatArgumentException(formatString.toString())
                    }
                    formatString.print(args.getOrNull(last), locale)
                }
                0 -> {
                    lastOrdinaryIndex++
                    last = lastOrdinaryIndex
                    if (lastOrdinaryIndex > args.size - 1) {
                        throw StringFormatterException.MissingFormatArgumentException(formatString.toString())
                    }
                    formatString.print(args.getOrNull(lastOrdinaryIndex), locale)
                }
                else -> {
                    last = index - 1
                    if (last > args.size - 1) {
                        throw StringFormatterException.MissingFormatArgumentException(formatString.toString())
                    }
                    formatString.print(args.getOrNull(last), locale)
                }
            }
        }
        return this
    }

    private fun parse(s: String): List<FormatString> {
        val result = mutableListOf<FormatString>()
        var index = 0
        while (index < s.length) {
            formatSpecifier.find(s, index)?.let { matchResult ->
                if (matchResult.range.first != index) {
                    checkText(s, index, matchResult.range.first)
                    result.add(FixedString(out, s, index, matchResult.range.first))
                }
                result.add(FormatSpecifier(out, matchResult))
                index = matchResult.range.last + 1
            } ?: run {
                checkText(s, index, s.length)
                result.add(FixedString(out, s, index, s.length))
                index = s.length
            }
        }
        return result
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

/**
 * List of exceptions that may be thrown by a [StringFormatter].
 */
sealed class StringFormatterException(message: String?) : Exception(message) {
    data class MissingFormatArgumentException(val formatSpecified: String) : StringFormatterException("Format specifier '$formatSpecified'")
    data class MissingFormatWidthException(val specification: String) : StringFormatterException("The specification, $specification, misses a required width.")
    data class UnknownFormatConversionException(val conversion: String) : StringFormatterException("Conversion = '$conversion'")
    data class UnexpectedChar(val currentCharacter: Char) : StringFormatterException("Unexpected Char $currentCharacter")
    data class DuplicateFormatFlagsException(val flags: String) : StringFormatterException("Flags = '$flags'")
    data class UnknownFormatFlagsException(val flags: String) : StringFormatterException("Flags = '$flags'")
    data class NumberFormatException(val exception: kotlin.NumberFormatException) : StringFormatterException(exception.message)
    data class IllegalFormatWidthException(val width: Int) : StringFormatterException("$width")
    data class IllegalFormatPrecisionException(val precision: Int) : StringFormatterException("$precision")
    data class IllegalFormatConversionException(val currentChar: Char, val any: Any) : StringFormatterException("$currentChar != $any")
    data class IllegalFormatFlagsException(val flags: String) : StringFormatterException("An illegal set of flags, $flags, was supplied.")
    data class MalformedValue(val value: Any) : StringFormatterException("Malformed Value: $value")
    data class FormatFlagsConversionMismatchException(val flags: String, val currentChar: Char) : StringFormatterException("Conversion = $currentChar, Flags = $flags")
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Locale

expect val lineSeparator: String

expect fun String.lowerCased(locale: Locale): String

expect fun String.upperCased(locale: Locale): String

package com.splendo.kaluga.base.utils
/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.

*/

/**
 * Applies a given code block if a given condition is met
 * @param condition The condition that should be met
 * @param block The code block to execute
 */
inline fun <T> T.applyIf(condition: Boolean, block: T.() -> Unit): T = apply {
    if (condition) block(this)
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

object ByteUtils {
    const val HEX_RADIX = 16
    const val BYTE_STRING_LENGTH = 2
}

/**
 * Returns [ByteArray] for given bytes
 */
fun bytesOf(vararg bytes: Int) = bytes.map(Int::toByte).toByteArray()

/**
 * Converts a [ByteArray] to a String representing the bytes as their hexadecimal value
 * @param separator The separator to use between elements
 * @return The String representing the [ByteArray]s hexadecimal value
 */
fun ByteArray.toHexString(separator: CharSequence = ""): String {
    return asUByteArray().joinToString(separator) {
        it.toString(ByteUtils.HEX_RADIX).padStart(ByteUtils.BYTE_STRING_LENGTH, '0')
    }
}

/**
 * Converts a hex [String] to [ByteArray]
 * @return The [ByteArray] or `null` if length is not even
 */
fun String.decodeHex(): ByteArray? {
    if (length.rem(ByteUtils.BYTE_STRING_LENGTH) != 0) return null

    return chunked(ByteUtils.BYTE_STRING_LENGTH) {
        it.toString().toInt(ByteUtils.HEX_RADIX).toByte()
    }.toByteArray()
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

import com.splendo.kaluga.base.utils.Locale.Companion.defaultLocale

/**
 * Class describing a point in time
 * Dates are localized according to a [Locale] and relative to a given [TimeZone]
 */
expect class Date : Comparable<Date> {
    companion object {
        /**
         * Creates a [Date] relative to the current time
         * @param offsetInMilliseconds The offset in milliseconds from the current time. Defaults to 0
         * @param timeZone The [TimeZone] in which the Date is set. Defaults to [TimeZone.current]
         * @param locale The [Locale] for which the Date is configured. Defaults to [Locale.defaultLocale]
         * @return A [Date] relative to the current time
         */
        fun now(offsetInMilliseconds: Long = 0L, timeZone: TimeZone = TimeZone.current(), locale: Locale = defaultLocale): Date

        /**
         * Creates a [Date] relative to January 1st 1970 00:00:00 GMT
         * @param offsetInMilliseconds The offset in milliseconds from the epoch time. Defaults to 0
         * @param timeZone The [TimeZone] in which the Date is set. Defaults to [TimeZone.current]
         * @param locale The [Locale] for which the Date is configured. Defaults to [Locale.defaultLocale]
         * @return A [Date] relative to the current time
         */
        fun epoch(offsetInMilliseconds: Long = 0L, timeZone: TimeZone = TimeZone.current(), locale: Locale = defaultLocale): Date
    }

    /**
     * The [TimeZone] in which the Date is set
     */
    var timeZone: TimeZone

    /**
     * The number of the era, e.g., AD or BC in the Julian calendar
     */
    var era: Int

    /**
     * The year
     */
    var year: Int

    /**
     * The month of the year. Starts at 1
     */
    var month: Int

    /**
     * The number of days in the current month
     */
    val daysInMonth: Int

    /**
     * The week number within the current year.
     */
    var weekOfYear: Int

    /**
     * The week number within the current month
     */
    var weekOfMonth: Int

    /**
     * The day of the current month
     */
    var day: Int

    /**
     * The day of the current year
     */
    var dayOfYear: Int

    /**
     * The day of the week. Starts at 1
     */
    var weekDay: Int

    /**
     * The first day of the week. E.g. Sunday in the US, Monday in France. Starts at 1.
     */
    var firstWeekDay: Int

    /**
     * The hour of the current day
     */
    var hour: Int

    /**
     * The minute of the current hour
     */
    var minute: Int

    /**
     * The second of the current minute
     */
    var second: Int

    /**
     * The millisecond of the current second
     */
    var millisecond: Int

    /**
     * The number of milliseconds passed since epoch time (January 1st 1970 00:00:00:00 GMT)
     */
    var millisecondSinceEpoch: Long

    /**
     * Creates a copy of a [Date]
     * @return A copy of this [Date]
     */
    fun copy(): Date

    /**
     * Returns whether this Date is in the same [timeZone] and has the same time based on [millisecondSinceEpoch]
     * @return `true` if the two dates are equal
     */
    override fun equals(other: Any?): Boolean

    override fun hashCode(): Int
}

/**
 * Creates a [Date] with the same [Locale] and [TimeZone] as the left date, but earlier by the right date millisecondSinceEpoch
 * @param date The [Date] of which the millisecondSinceEpoch to subtract should be retrieved
 * @return A new [Date] with the same [Locale] and [TimeZone] as the left date, but earlier by the right date millisecondSinceEpoch
 */
operator fun Date.minus(date: Date): Date {
    return copy().apply {
        millisecondSinceEpoch -= date.millisecondSinceEpoch
    }
}

/**
 * Creates a [Date] with the same [Locale] and [TimeZone] as the left date, but later by the right date millisecondSinceEpoch
 * @param date The [Date] of which the millisecondSinceEpoch to add should be retrieved
 * @return A new [Date] with the same [Locale] and [TimeZone] as the left date, but later by the right date millisecondSinceEpoch
 */
operator fun Date.plus(date: Date): Date {
    return copy().apply {
        millisecondSinceEpoch += date.millisecondSinceEpoch
    }
}

/**
 * Creates a [Date] relative to the current time, in the UTC timezone
 * @param offsetInMilliseconds The offset in milliseconds from the current time. Defaults to 0
 * @param locale The [Locale] for which the Date is configured. Defaults to [Locale.defaultLocale]
 * @return A [Date] relative to the current time, in the UTC timezone
 */
fun Date.Companion.nowUtc(offsetInMilliseconds: Long = 0L, locale: Locale = defaultLocale): Date =
    now(offsetInMilliseconds, TimeZone.utc, locale)

/**
 * Gets a [Date] equal to midnight on the same day as this Date
 * @return A [Date] equal to midnight on the same day as this Date
 */
fun Date.toStartOfDay() = this.copy().apply {
    hour = 0
    minute = 0
    second = 0
    millisecond = 0
}

/**
 * Gets a [Date] that is set at midnight on the same day as the current time.
 * @param timeZone The [TimeZone] in which the Date is set. Defaults to [TimeZone.current]
 * @param locale The [Locale] for which the Date is configured. Defaults to [Locale.defaultLocale]
 * @return A [Date] that is set at midnight on the same day as the current time.
 */
fun Date.Companion.today(timeZone: TimeZone = TimeZone.current(), locale: Locale = defaultLocale) = now(timeZone = timeZone, locale = locale).toStartOfDay()

/**
 * Gets a [Date] that is set at midnight on the day after the current time.
 * @param timeZone The [TimeZone] in which the Date is set. Defaults to [TimeZone.current]
 * @param locale The [Locale] for which the Date is configured. Defaults to [Locale.defaultLocale]
 * @return A [Date] that is set at midnight on the day after the current time.
 */
fun Date.Companion.tomorrow(timeZone: TimeZone = TimeZone.current(), locale: Locale = defaultLocale) = today(timeZone = timeZone, locale = locale).apply { day += 1 }

/**
 * Checks whether a [Date] is on the same day as a given Date.
 * @param date The [Date] to check
 * @return `true` if this [Date] is on the same day as [date]
 */
fun Date.isOnSameDay(date: Date): Boolean {
    return this.era == date.era &&
        this.year == date.year &&
        this.month == date.month &&
        this.day == date.day
}

/**
 * Checks whether a [Date] is on the same month as a given Date.
 * @param date The [Date] to check
 * @return `true` if this [Date] is on the same month as [date]
 */
fun Date.isOnSameMonth(date: Date): Boolean {
    return this.era == date.era &&
        this.year == date.year &&
        this.month == date.month
}

/**
 * Checks whether a [Date] is in the same year as a given Date.
 * @param date The [Date] to check
 * @return `true` if this [Date] is in the same year as [date]
 */
fun Date.isInSameYear(date: Date): Boolean {
    return this.era == date.era &&
        this.year == date.year
}

/**
 * True if this [Date] is today
 */
val Date.isToday: Boolean
    get() = isOnSameDay(Date.now(timeZone = this.timeZone))

/**
 * True if this [Date] is yesterday
 */
val Date.isYesterday: Boolean
    get() = isOnSameDay(Date.now(timeZone = this.timeZone).apply { day -= 1 })

/**
 * True if this [Date] is tomorrow
 */
val Date.isTomorrow: Boolean
    get() = isOnSameDay(Date.now(timeZone = this.timeZone).apply { day += 1 })

/**
 * True if this [Date] is this month
 */
val Date.isThisMonth: Boolean
    get() = isOnSameMonth(Date.now(timeZone = this.timeZone))

/**
 * True if this [Date] is last month
 */
val Date.isLastMonth: Boolean
    get() = isOnSameMonth(Date.now(timeZone = this.timeZone).apply { month -= 1 })

/**
 * True if this [Date] is next month
 */
val Date.isNextMonth: Boolean
    get() = isOnSameMonth(Date.now(timeZone = this.timeZone).apply { month += 1 })

/**
 * True if this [Date] is this year
 */
val Date.isThisYear: Boolean
    get() = isInSameYear(Date.now(timeZone = this.timeZone))

/**
 * True if this [Date] is last year
 */
val Date.isLastYear: Boolean
    get() = isInSameYear(Date.now(timeZone = this.timeZone).apply { year -= 1 })

/**
 * True if this [Date] is next year
 */
val Date.isNextYear: Boolean
    get() = isInSameYear(Date.now(timeZone = this.timeZone).apply { year += 1 })

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

expect class Decimal : Comparable<Decimal>

expect operator fun Decimal.plus(value: Decimal): Decimal
expect fun Decimal.plus(value: Decimal, scale: Int): Decimal
expect fun Decimal.plus(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode = RoundingMode.RoundHalfEven
): Decimal

expect operator fun Decimal.minus(value: Decimal): Decimal
expect fun Decimal.minus(value: Decimal, scale: Int): Decimal
expect fun Decimal.minus(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode = RoundingMode.RoundHalfEven
): Decimal

expect operator fun Decimal.div(value: Decimal): Decimal
expect fun Decimal.div(value: Decimal, scale: Int): Decimal
expect fun Decimal.div(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode = RoundingMode.RoundHalfEven
): Decimal

expect operator fun Decimal.times(value: Decimal): Decimal
expect fun Decimal.times(value: Decimal, scale: Int): Decimal
expect fun Decimal.times(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode = RoundingMode.RoundHalfEven
): Decimal

expect infix fun Decimal.pow(n: Int): Decimal
expect fun Decimal.pow(n: Int, scale: Int): Decimal
expect fun Decimal.pow(
    n: Int,
    scale: Int,
    roundingMode: RoundingMode = RoundingMode.RoundHalfEven
): Decimal

sealed class RoundingMode {
    object RoundDown : RoundingMode()
    object RoundHalfEven : RoundingMode()
    object RoundUp : RoundingMode()
}

expect fun Decimal.round(scale: Int, roundingMode: RoundingMode = RoundingMode.RoundHalfEven): Decimal

expect fun Number.toDecimal(): Decimal
expect fun String.toDecimal(): Decimal

fun ByteArray.toDecimalList(): List<Decimal> = map { it.toDecimal() }
fun DoubleArray.toDecimalList(): List<Decimal> = map { it.toDecimal() }
fun FloatArray.toDecimalList(): List<Decimal> = map { it.toDecimal() }
fun IntArray.toDecimalList(): List<Decimal> = map { it.toDecimal() }
fun ShortArray.toDecimalList(): List<Decimal> = map { it.toDecimal() }
fun LongArray.toDecimalList(): List<Decimal> = map { it.toDecimal() }
fun <NumberType : Number> Collection<NumberType>.toDecimalList(): List<Decimal> = map { it.toDecimal() }

expect fun Decimal.toDouble(): Double
expect fun Decimal.toString(): String
expect fun Decimal.toInt(): Int

fun List<Decimal>.toDoubleArray(): DoubleArray = map { it.toDouble() }.toDoubleArray()
fun List<Decimal>.toStringList(): List<String> = map { it.toString() }
fun List<Decimal>.toIntArray(): IntArray = map { it.toInt() }.toIntArray()

package com.splendo.kaluga.base.utils
/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.

*/

import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Job

fun EmptyCompletableDeferred.complete() {
    complete(Unit)
}

typealias EmptyCompletableDeferred = CompletableDeferred<Unit>

@Suppress("FunctionName") // Kotlin convention to provide package methods that look like constructors
/**
 * Creates a [CompletableDeferred] in an _active_ state of typed [Unit].
 * In other words, it returns no value (either it will complete or throw an Exception).
 * It is optionally a child of a [parent] job.
 */
fun EmptyCompletableDeferred(parent: Job? = null): EmptyCompletableDeferred = CompletableDeferred(parent)

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.base.utils

/**
 * Returns the enum by matching ordinals position, or the given default value if no ordinals match.
 *
 * @param ordinal The ordinal value in this enum
 * @param defaultValue The default value to return if the ordinal is out of range
 *
 * @return The enum value matching the ordinal, or the default.
 *
 */
inline fun <reified T : Enum<T>> Enum.Companion.byOrdinalOrDefault(ordinal: Int, defaultValue: T): T {
    return if (ordinal !in enumValues<T>().indices)
        defaultValue
    else
        enumValues<T>()[ordinal]
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

import kotlin.math.abs
import kotlin.math.log10

fun Int.length() = when (this) {
    0 -> 1
    else -> log10(abs(toDouble())).toInt() + 1
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

import com.splendo.kaluga.base.text.DateFormatStyle
import com.splendo.kaluga.base.text.DateFormatter

/**
 * A [Locale] object represents a specific geographical, political, or cultural region.
 */
expect class Locale {
    companion object {

        /**
         * Creates a [Locale] based on a `language` ISO 639 alpha-2 or alpha-3 code
         * @param language a `language` ISO 639 alpha-2 or alpha-3 code.
         * @return The [Locale] for the given [language]
         */
        fun createLocale(language: String): Locale

        /**
         * Creates a [Locale] based on a ISO 639 alpha-2 or alpha-3 `language` code and ISO 3166 alpha-2 `country` code.
         * @param language a ISO 639 alpha-2 or alpha-3 `language` code.
         * @param country a ISO 3166 alpha-2 `country` code.
         * @return The [Locale] for the given [language] and [country]
         */
        fun createLocale(language: String, country: String): Locale

        /**
         * Creates a [Locale] based on a ISO 639 alpha-2 or alpha-3 `language` code, ISO 3166 alpha-2 `country` code, and variant code.
         * @param language a ISO 639 alpha-2 or alpha-3 `language` code.
         * @param country a ISO 3166 alpha-2 `country` code.
         * @param variant Arbitrary value used to indicate a variation of a [Locale]
         * @return The [Locale] for the given [language], [country], and [variant]
         */
        fun createLocale(language: String, country: String, variant: String): Locale

        val defaultLocale: Locale
        val availableLocales: List<Locale>
    }

    /**
     *  ISO 3166 alpha-2 country code or UN M.49 numeric-3 area code.
     *
     *  Example: "US" (United States), "FR" (France), "029" (Caribbean)
     */
    val countryCode: String

    /**
     * ISO 639 alpha-2 or alpha-3 language code.
     *
     * Example: "en" (English), "ja" (Japanese), "kok" (Konkani)
     */
    val languageCode: String

    /**
     * ISO 15924 alpha-4 script code.
     *
     * Example: "Latn" (Latin), "Cyrl" (Cyrillic)
     */
    val scriptCode: String

    /**
     * Any arbitrary value used to indicate a variation of a [Locale].
     * Where there are two or more variant values each indicating its own semantics, these values should be ordered by importance, with most important first, separated by underscore('_').
     *
     * Example: "polyton" (Polytonic Greek), "POSIX"
     */
    val variantCode: String

    /**
     * Returns [UnitSystem] for the [Locale].
     */
    val unitSystem: UnitSystem

    /**
     * Gets the name of the [Locale], localized according to a given [Locale]
     * @param forLocale The [Locale] for which the name should be localized
     * @return The name of this [Locale]
     */
    fun name(forLocale: Locale): String

    /**
     * Gets the county name of the [Locale], localized according to a given [Locale]
     * @param forLocale The [Locale] for which the country name should be localized
     * @return The country name of this [Locale]
     */
    fun countryName(forLocale: Locale): String

    /**
     * Gets the language name of the [Locale], localized according to a given [Locale]
     * @param forLocale The [Locale] for which the language name should be localized
     * @return The language name of this [Locale]
     */
    fun languageName(forLocale: Locale): String

    /**
     * Gets the variant name of the [Locale], localized according to a given [Locale]
     * @param forLocale The [Locale] for which the variant name should be localized
     * @return The variant name of this [Locale]
     */
    fun variantName(forLocale: Locale): String

    /**
     * Gets the script name of the [Locale], localized according to a given [Locale]
     * @param forLocale The [Locale] for which the script name should be localized
     * @return The script name of this [Locale]
     */
    fun scriptName(forLocale: Locale): String

    /**
     * The Character(s) used for indicating the start of a quote
     */
    val quotationStart: String
    /**
     * The Character(s) used for indicating the end of a quote
     */
    val quotationEnd: String
    /**
     * The alternative Character(s) used for indicating the start of a quote
     */
    val alternateQuotationStart: String
    /**
     * The alternative Character(s) used for indicating the end of a quote
     */
    val alternateQuotationEnd: String
}

/**
 * Locale for English/US in a POSIX format. Useful shortcut when dealing with fixed locale formats.
 */
val Locale.Companion.enUsPosix get() = createLocale("en", "US", "POSIX")

/**
 * Indicates whether this locale use a 24 hour clock cycle.
 */
val Locale.uses24HourClock: Boolean get() {
    val formatter = DateFormatter.timeFormat(DateFormatStyle.Medium, locale = this)
    val formattedDate = formatter.format(Date.now())
    return !formattedDate.contains(formatter.amString) && !formattedDate.contains(formatter.pmString)
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

expect interface Serializable

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

import com.splendo.kaluga.base.utils.Date.Companion.now
import com.splendo.kaluga.base.utils.Locale.Companion.defaultLocale
import kotlin.native.concurrent.ThreadLocal

/**
 * Style for writing the name of a [TimeZone]
 * Can be either [Short] or [Long]
 */
enum class TimeZoneNameStyle {
    Short,
    Long
}

@ThreadLocal // one instance per thread is preferred over a non-lazy initialization, since timezones are heavy to instantiate
val TimeZone.Companion.utc: TimeZone by lazy { TimeZone.get("UTC")!! }

expect class TimeZone {
    companion object {
        /**
         * Gets a [TimeZone] based on a given Identifier
         * @param identifier The identifier to create a [TimeZone] for
         * @return The [TimeZone] corresponding to the identifier, if it exists. Check [availableIdentifiers] for supported identifiers
         */
        fun get(identifier: String): TimeZone?

        /**
         * Gets the current [TimeZone] configured by the user
         * @return The current [TimeZone] of the user
         */
        fun current(): TimeZone

        /**
         * List of available identifiers associated with [TimeZone]s. All elements in this list can be used for creating a [TimeZone] using [TimeZone.get]
         */
        val availableIdentifiers: List<String>
    }

    /**
     * The identifier of the [TimeZone]
     */
    val identifier: String

    /**
     * Gets the display name for this [TimeZone] formatted for a given [Locale]
     * @param style The [TimeZoneNameStyle] of the name. Can be either [TimeZoneNameStyle.Short] or [TimeZoneNameStyle.Long]
     * @param withDaylightSavings If `true` the name for the DaylightSavings version of the [TimeZone] will be used. Defaults to [TimeZone.usesDaylightSavingsTime]
     * @param locale The [Locale] used for naming the [TimeZone]. Defaults to [Locale.defaultLocale]
     */
    fun displayName(style: TimeZoneNameStyle, withDaylightSavings: Boolean = usesDaylightSavingsTime(), locale: Locale = defaultLocale): String

    /**
     * The number of milliseconds this timezone differs from GMT when daylight savings time is not active
     */
    val offsetFromGMTInMilliseconds: Long

    /**
     * The number of milliseconds this timezone differs from itself during Daylight Savings
     */
    val daylightSavingsOffsetInMilliseconds: Long

    /**
     * The number of milliseconds this [TimeZone] differs from GMT at a given [Date]
     * @param date The [Date] for which to check the offset. Defaults to [Date.now]
     * @return The number of milliseconds this [TimeZone] differs from GMT at [date]
     */
    fun offsetFromGMTAtDateInMilliseconds(date: Date = now()): Long

    /**
     * Returns `true` if this [TimeZone] is observing daylight savings at a given [Date]
     * @param date The [Date] for which to check whether daylight savings is observed. Defaults to [Date.now]
     * @return `true` if this [TimeZone] if observing daylight savings at [date]
     */
    fun usesDaylightSavingsTime(date: Date = now()): Boolean

    /**
     * Creates a copy of this [TimeZone]
     */
    fun copy(): TimeZone
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

enum class UnitSystem {

    METRIC, MIXED, IMPERIAL;

    companion object {
        // FIXME IMPERIAL and US customary seem reversed
        fun withRawValue(value: String): UnitSystem = when (value) {
            "U.S." -> IMPERIAL
            "U.K." -> MIXED
            else -> METRIC
        }

        // FIXME IMPERIAL and US customary seem reversed
        fun withCountryCode(code: String): UnitSystem = when {
            listOf("GB", "MM", "LR").contains(code) -> MIXED
            "US" == code -> IMPERIAL
            else -> METRIC
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.firstOrNull

/**
 * Returns first element that is instance of specific type parameter [R], or `null` if not found
 */
inline fun <reified R> Sequence<*>.firstInstance(): R? = filterIsInstance<R>().firstOrNull()

/**
 * Returns first element that is instance of specific type parameter [R], or `null` if not found
 */
suspend inline fun <reified R> Flow<*>.firstInstance(): R? = filterIsInstance<R>().firstOrNull()

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.typedList
import com.splendo.kaluga.base.utils.Date
import com.splendo.kaluga.base.utils.Locale
import com.splendo.kaluga.base.utils.TimeZone
import platform.Foundation.NSCalendar
import platform.Foundation.NSDateFormatter
import platform.Foundation.NSDateFormatterFullStyle
import platform.Foundation.NSDateFormatterLongStyle
import platform.Foundation.NSDateFormatterMediumStyle
import platform.Foundation.NSDateFormatterNoStyle
import platform.Foundation.NSDateFormatterShortStyle
import platform.Foundation.NSDateFormatterStyle

actual class DateFormatter private constructor(private val format: NSDateFormatter) {

    actual companion object {
        actual fun dateFormat(
            style: DateFormatStyle,
            timeZone: TimeZone,
            locale: Locale
        ): DateFormatter = createDateFormatter(style, null, timeZone, locale)

        actual fun timeFormat(
            style: DateFormatStyle,
            timeZone: TimeZone,
            locale: Locale
        ): DateFormatter = createDateFormatter(null, style, timeZone, locale)

        actual fun dateTimeFormat(
            dateStyle: DateFormatStyle,
            timeStyle: DateFormatStyle,
            timeZone: TimeZone,
            locale: Locale
        ): DateFormatter = createDateFormatter(dateStyle, timeStyle, timeZone, locale)

        actual fun patternFormat(pattern: String, timeZone: TimeZone, locale: Locale): DateFormatter = DateFormatter(
            NSDateFormatter().apply {
                this.locale = locale.nsLocale
                this.timeZone = timeZone.timeZone
                dateFormat = pattern
            }
        )

        fun createDateFormatter(
            dateStyle: DateFormatStyle?,
            timeStyle: DateFormatStyle?,
            timeZone: TimeZone,
            locale: Locale
        ): DateFormatter = DateFormatter(
            NSDateFormatter().apply {
                this.locale = locale.nsLocale
                this.timeZone = timeZone.timeZone
                this.dateStyle = dateStyle.nsDateFormatterStyle()
                this.timeStyle = timeStyle.nsDateFormatterStyle()
            }
        )
    }

    actual var pattern: String
        get() = format.dateFormat
        set(value) { format.dateFormat = value }

    actual var timeZone: TimeZone
        get() = TimeZone(format.timeZone)
        set(value) { format.timeZone = value.timeZone }

    actual var eras: List<String>
        get() = format.eraSymbols.typedList()
        set(value) { format.eraSymbols = value }

    actual var months: List<String>
        get() = format.monthSymbols.typedList()
        set(value) { format.monthSymbols = value }
    actual var shortMonths: List<String>
        get() = format.shortMonthSymbols.typedList()
        set(value) { format.shortMonthSymbols = value }

    actual var weekdays: List<String>
        get() = format.weekdaySymbols.typedList()
        set(value) { format.weekdaySymbols = value }
    actual var shortWeekdays: List<String>
        get() = format.shortWeekdaySymbols.typedList()
        set(value) { format.shortWeekdaySymbols = value }

    actual var amString: String
        get() = format.AMSymbol
        set(value) { format.AMSymbol = value }
    actual var pmString: String
        get() = format.PMSymbol
        set(value) { format.PMSymbol = value }

    actual fun format(date: Date): String = format.stringFromDate(date.date)
    actual fun parse(string: String): Date? {
        return format.dateFromString(string)?.let { date ->
            val calendar = format.calendar.copy() as NSCalendar
            Date(calendar, date)
        }
    }
}

private fun DateFormatStyle?.nsDateFormatterStyle(): NSDateFormatterStyle = when (this) {
    DateFormatStyle.Short -> NSDateFormatterShortStyle
    DateFormatStyle.Medium -> NSDateFormatterMediumStyle
    DateFormatStyle.Long -> NSDateFormatterLongStyle
    DateFormatStyle.Full -> NSDateFormatterFullStyle
    null -> NSDateFormatterNoStyle
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Locale
import platform.Foundation.NSNumber
import platform.Foundation.NSNumberFormatter
import platform.Foundation.NSNumberFormatterCurrencyStyle
import platform.Foundation.NSNumberFormatterDecimalStyle
import platform.Foundation.NSNumberFormatterPercentStyle
import platform.Foundation.NSNumberFormatterRoundCeiling
import platform.Foundation.NSNumberFormatterRoundDown
import platform.Foundation.NSNumberFormatterRoundFloor
import platform.Foundation.NSNumberFormatterRoundHalfDown
import platform.Foundation.NSNumberFormatterRoundHalfEven
import platform.Foundation.NSNumberFormatterRoundUp
import platform.Foundation.NSNumberFormatterScientificStyle
import platform.Foundation.numberWithInt
import platform.darwin.NSUInteger

actual class NumberFormatter actual constructor(actual val locale: Locale, style: NumberFormatStyle) {

    @ExperimentalUnsignedTypes
    private val formatter = NSNumberFormatter().apply {
        locale = this@NumberFormatter.locale.nsLocale
        when (style) {
            is NumberFormatStyle.Integer -> {
                numberStyle = NSNumberFormatterDecimalStyle
                minimumIntegerDigits = style.minDigits.toULong() as NSUInteger
                maximumIntegerDigits = style.maxDigits.toULong() as NSUInteger
                minimumFractionDigits = 0.toULong() as NSUInteger
                maximumFractionDigits = 0.toULong() as NSUInteger
            }
            is NumberFormatStyle.Decimal -> {
                numberStyle = NSNumberFormatterDecimalStyle
                minimumIntegerDigits = style.minIntegerDigits.toULong() as NSUInteger
                maximumIntegerDigits = style.maxIntegerDigits.toULong() as NSUInteger
                minimumFractionDigits = style.minFractionDigits.toULong() as NSUInteger
                maximumFractionDigits = style.maxFractionDigits.toULong() as NSUInteger
            }
            is NumberFormatStyle.Percentage -> {
                numberStyle = NSNumberFormatterPercentStyle
                minimumIntegerDigits = style.minIntegerDigits.toULong() as NSUInteger
                maximumIntegerDigits = style.maxIntegerDigits.toULong() as NSUInteger
                minimumFractionDigits = style.minFractionDigits.toULong() as NSUInteger
                maximumFractionDigits = style.maxFractionDigits.toULong() as NSUInteger
            }
            is NumberFormatStyle.Permillage -> {
                numberStyle = NSNumberFormatterPercentStyle
                positiveFormat = positiveFormat.replace('%', '‰')
                negativeFormat = negativeFormat.replace('%', '‰')
                minimumIntegerDigits = style.minIntegerDigits.toULong() as NSUInteger
                maximumIntegerDigits = style.maxIntegerDigits.toULong() as NSUInteger
                minimumFractionDigits = style.minFractionDigits.toULong() as NSUInteger
                maximumFractionDigits = style.maxFractionDigits.toULong() as NSUInteger
            }
            is NumberFormatStyle.Scientific -> {
                numberStyle = NSNumberFormatterScientificStyle
                positiveFormat = style.pattern
                negativeFormat = "-${style.pattern}"
            }
            is NumberFormatStyle.Currency -> {
                numberStyle = NSNumberFormatterCurrencyStyle
                style.currencyCode?.let { currencyCode ->
                    this.currencyCode = currencyCode
                }
                minimumIntegerDigits = style.minIntegerDigits.toULong() as NSUInteger
                maximumIntegerDigits = style.maxIntegerDigits.toULong() as NSUInteger
                style.minFractionDigits?.let {
                    minimumFractionDigits = it.toULong() as NSUInteger
                }
                style.maxFractionDigits?.let {
                    maximumFractionDigits = it.toULong() as NSUInteger
                }
            }
            is NumberFormatStyle.Pattern -> {
                numberStyle = NSNumberFormatterDecimalStyle
                positiveFormat = style.positivePattern
                negativeFormat = style.negativePattern
            }
        }
        usesSignificantDigits = false
        roundingMode = when (style.roundingMode) {
            RoundingMode.Ceiling -> NSNumberFormatterRoundCeiling
            RoundingMode.Floor -> NSNumberFormatterRoundFloor
            RoundingMode.HalfEven -> NSNumberFormatterRoundHalfEven
            RoundingMode.HalfUp -> NSNumberFormatterRoundFloor
            RoundingMode.HalfDown -> NSNumberFormatterRoundHalfDown
            RoundingMode.Down -> NSNumberFormatterRoundDown
            RoundingMode.Up -> NSNumberFormatterRoundUp
        }
    }

    actual var percentSymbol: Char
        get() = formatter.percentSymbol.getOrNull(0) ?: Char.MIN_VALUE
        set(value) { formatter.percentSymbol = charArrayOf(value).concatToString() }
    actual var perMillSymbol: Char
        get() = formatter.perMillSymbol.getOrNull(0) ?: Char.MIN_VALUE
        set(value) { formatter.perMillSymbol = charArrayOf(value).concatToString() }
    actual var minusSign: Char
        get() = formatter.minusSign.getOrNull(0) ?: Char.MIN_VALUE
        set(value) { formatter.minusSign = charArrayOf(value).concatToString() }
    actual var exponentSymbol: String
        get() = formatter.exponentSymbol
        set(value) { formatter.exponentSymbol = value }
    actual var zeroSymbol: Char
        get() = formatter.zeroSymbol?.getOrNull(0) ?: '0'
        set(value) { formatter.zeroSymbol = charArrayOf(value).concatToString() }
    actual var notANumberSymbol: String
        get() = formatter.notANumberSymbol
        set(value) { formatter.notANumberSymbol = value }
    actual var infinitySymbol: String
        get() = formatter.positiveInfinitySymbol
        set(value) {
            formatter.positiveInfinitySymbol = value
            formatter.negativeInfinitySymbol = value
        }
    actual var currencySymbol: String
        get() = formatter.currencySymbol
        set(value) { formatter.currencySymbol = value }
    actual var currencyCode: String
        get() = formatter.currencyCode
        set(value) { formatter.currencyCode = value }
    actual var positivePrefix: String
        get() = formatter.positivePrefix
        set(value) { formatter.positivePrefix = value }
    actual var positiveSuffix: String
        get() = formatter.positiveSuffix
        set(value) { formatter.positiveSuffix = value }
    actual var negativePrefix: String
        get() = formatter.negativePrefix
        set(value) { formatter.negativePrefix = value }
    actual var negativeSuffix: String
        get() = formatter.negativeSuffix
        set(value) { formatter.negativeSuffix = value }
    actual var groupingSeparator: Char
        get() = formatter.groupingSeparator.getOrNull(0) ?: Char.MIN_VALUE
        set(value) {
            val charValue = charArrayOf(value).concatToString()
            formatter.groupingSeparator = charValue
            formatter.currencyGroupingSeparator = charValue
        }
    actual var usesGroupingSeparator: Boolean
        get() = formatter.usesGroupingSeparator
        set(value) { formatter.usesGroupingSeparator = value }
    actual var decimalSeparator: Char
        get() = formatter.decimalSeparator.getOrNull(0) ?: Char.MIN_VALUE
        set(value) { formatter.decimalSeparator = charArrayOf(value).concatToString() }
    actual var alwaysShowsDecimalSeparator: Boolean
        get() = formatter.alwaysShowsDecimalSeparator
        set(value) { formatter.alwaysShowsDecimalSeparator = value }
    actual var currencyDecimalSeparator: Char
        get() = formatter.currencyDecimalSeparator.getOrNull(0) ?: Char.MIN_VALUE
        set(value) { formatter.currencyDecimalSeparator = charArrayOf(value).concatToString() }
    actual var groupingSize: Int
        get() = formatter.groupingSize.toInt()
        set(value) {
            formatter.groupingSize = value.toULong() as NSUInteger
            formatter.secondaryGroupingSize = value.toULong() as NSUInteger
        }
    actual var multiplier: Int
        get() = formatter.multiplier?.intValue ?: 1
        set(value) { formatter.multiplier = NSNumber.numberWithInt(value) }

    actual fun format(number: Number): String {
        return (formatter.stringFromNumber(number as NSNumber) ?: "")
    }

    actual fun parse(string: String): Number? {
        return formatter.numberFromString(string) as? Number
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Locale
import platform.Foundation.NSString
import platform.Foundation.lowercaseStringWithLocale
import platform.Foundation.uppercaseStringWithLocale

actual val lineSeparator = "\n"

actual fun String.lowerCased(locale: Locale): String = (this as NSString).lowercaseStringWithLocale(locale.nsLocale)

actual fun String.upperCased(locale: Locale): String = (this as NSString).uppercaseStringWithLocale(locale.nsLocale)

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base

import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.allocArrayOf
import kotlinx.cinterop.get
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.reinterpret
import platform.Foundation.NSData
import platform.Foundation.create
import platform.darwin.NSUInteger

/**
 * Converts a [NSData] to its corresponding [ByteArray]
 * @return The [ByteArray] equivalent to this [NSData]
 */
fun NSData.toByteArray(): ByteArray {
    val bytes = bytes?.let { it } ?: return byteArrayOf()
    val ktBytes: CPointer<ByteVar> = bytes.reinterpret()
    return ByteArray(length.toInt()) { index -> ktBytes[index] }
}

/**
 * Converts a [ByteArray] to its corresponding [NSData]
 * @return Th [NSData] corresponding to this [ByteArray]
 */
fun ByteArray.toNSData(): NSData = memScoped {
    return NSData.create(bytes = allocArrayOf(this@toNSData), length = this@toNSData.size.toULong() as NSUInteger)
}

/**
 * Converts a list of unknown typing to a list of a given type.
 * This proves useful since generics are lost when converting ObjC/Swift to Kotlin
 * @return The list of all the elements in the given list that match the desired typing
 */
inline fun <reified T : Any> List<*>.typedList(): List<T> {
    return mapNotNull {
        when (it) {
            is T -> it
            else -> null
        }
    }
}

/**
 * Converts a map of unknown typing to a map of a given key-value type.
 * This proves useful since generics are lost when converting ObjC/Swift to Kotlin
 * @return The map of all the elements in the given map that match the desired typing
 */
inline fun <reified K : Any, reified V : Any> Map<*, *>.typedMap(): Map<K, V> {
    return this.mapNotNull { entry ->
        when (val key = entry.key) {
            is K -> {
                when (val value = entry.value) {
                    is V -> Pair(key, value)
                    else -> null
                }
            }
            else -> null
        }
    }.toMap()
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

import kotlinx.cinterop.useContents
import platform.Foundation.NSCalendar
import platform.Foundation.NSCalendarMatchNextTimePreservingSmallerUnits
import platform.Foundation.NSCalendarMatchPreviousTimePreservingSmallerUnits
import platform.Foundation.NSCalendarOptions
import platform.Foundation.NSCalendarSearchBackwards
import platform.Foundation.NSCalendarUnit
import platform.Foundation.NSCalendarUnitDay
import platform.Foundation.NSCalendarUnitEra
import platform.Foundation.NSCalendarUnitHour
import platform.Foundation.NSCalendarUnitMinute
import platform.Foundation.NSCalendarUnitMonth
import platform.Foundation.NSCalendarUnitNanosecond
import platform.Foundation.NSCalendarUnitSecond
import platform.Foundation.NSCalendarUnitWeekOfMonth
import platform.Foundation.NSCalendarUnitWeekOfYear
import platform.Foundation.NSCalendarUnitWeekday
import platform.Foundation.NSCalendarUnitYear
import platform.Foundation.NSDate
import platform.Foundation.compare
import platform.Foundation.dateWithTimeIntervalSince1970
import platform.Foundation.dateWithTimeIntervalSinceNow
import platform.Foundation.timeIntervalSince1970
import platform.darwin.NSInteger
import platform.darwin.NSUInteger
import kotlin.math.round

actual class Date internal constructor(private val calendar: NSCalendar, initialDate: NSDate) : Comparable<Date> {
    actual companion object {

        const val nanoSecondPerMilliSecond = 1000 * 1000

        actual fun now(offsetInMilliseconds: Long, timeZone: TimeZone, locale: Locale): Date {
            val calendar = NSCalendar.currentCalendar.apply {
                this.locale = locale.nsLocale
                this.timeZone = timeZone.timeZone
            }
            val date = NSDate.dateWithTimeIntervalSinceNow(offsetInMilliseconds.toDouble() / 1000.0)
            return Date(calendar, date)
        }
        actual fun epoch(offsetInMilliseconds: Long, timeZone: TimeZone, locale: Locale): Date {
            val calendar = NSCalendar.currentCalendar.apply {
                this.locale = locale.nsLocale
                this.timeZone = timeZone.timeZone
            }
            val date = NSDate.dateWithTimeIntervalSince1970(offsetInMilliseconds.toDouble() / 1000.0)
            return Date(calendar, date)
        }
    }

    internal var date: NSDate = initialDate

    actual var timeZone: TimeZone
        get() = TimeZone(calendar.timeZone)
        set(value) { calendar.timeZone = value.timeZone }
    actual var era: Int
        get() = calendar.component(NSCalendarUnitEra, fromDate = date).toInt()
        set(value) { updateDateForComponent(NSCalendarUnitEra, value) }
    actual var year: Int
        get() = calendar.component(NSCalendarUnitYear, fromDate = date).toInt()
        set(value) { updateDateForComponent(NSCalendarUnitYear, value) }
    actual var month: Int
        get() = calendar.component(NSCalendarUnitMonth, fromDate = date).toInt()
        set(value) { updateDateForComponent(NSCalendarUnitMonth, value) }
    actual val daysInMonth: Int get() = calendar.rangeOfUnit(NSCalendarUnitDay, NSCalendarUnitMonth, forDate = date).useContents { this.length.toInt() }
    actual var weekOfYear: Int
        get() = calendar.component(NSCalendarUnitWeekOfYear, fromDate = date).toInt()
        set(value) { updateDateForComponent(NSCalendarUnitWeekOfYear, value) }
    actual var weekOfMonth: Int
        get() = calendar.component(NSCalendarUnitWeekOfMonth, fromDate = date).toInt()
        set(value) { updateDateForComponent(NSCalendarUnitWeekOfMonth, value) }
    actual var day: Int
        get() = calendar.component(NSCalendarUnitDay, fromDate = date).toInt()
        set(value) { updateDateForComponent(NSCalendarUnitDay, value) }
    actual var dayOfYear: Int
        get() = calendar.ordinalityOfUnit(NSCalendarUnitDay, NSCalendarUnitYear, date).toInt()
        set(value) { updateDateForComponent(NSCalendarUnitDay, value - dayOfYear + day) }
    actual var weekDay: Int
        get() = calendar.component(NSCalendarUnitWeekday, fromDate = date).toInt()
        set(value) { updateDateForComponent(NSCalendarUnitWeekday, value) }
    actual var firstWeekDay: Int
        get() = (calendar.firstWeekday.toInt())
        set(value) { calendar.firstWeekday = value.toULong() as NSUInteger }

    actual var hour: Int
        get() = calendar.component(NSCalendarUnitHour, fromDate = date).toInt()
        set(value) { updateDateForComponent(NSCalendarUnitHour, value) }
    actual var minute: Int
        get() = calendar.component(NSCalendarUnitMinute, fromDate = date).toInt()
        set(value) { updateDateForComponent(NSCalendarUnitMinute, value) }
    actual var second: Int
        get() = calendar.component(NSCalendarUnitSecond, fromDate = date).toInt()
        set(value) { updateDateForComponent(NSCalendarUnitSecond, value) }
    actual var millisecond: Int
        get() = calendar.component(NSCalendarUnitNanosecond, fromDate = date).toInt() / nanoSecondPerMilliSecond
        set(value) { updateDateForComponent(NSCalendarUnitNanosecond, value * nanoSecondPerMilliSecond) }
    actual var millisecondSinceEpoch: Long
        get() {
            val time = date.timeIntervalSince1970
            val decimalDigits = (time % 1.0) * 1000
            return time.toLong() * 1000L + round(decimalDigits).toLong()
        }
        set(value) { date = NSDate.dateWithTimeIntervalSince1970(value.toDouble() / 1000.0) }

    actual fun copy(): Date = Date(calendar.copy() as NSCalendar, date.copy() as NSDate)

    actual override fun equals(other: Any?): Boolean {
        return (other as? Date)?.let { other ->
            calendar.calendarIdentifier == other.calendar.calendarIdentifier && millisecondSinceEpoch == other.millisecondSinceEpoch && this.calendar.timeZone == other.calendar.timeZone
        } ?: false
    }

    actual override fun hashCode(): Int {
        var result = calendar.calendarIdentifier.hashCode()
        result = 31 * result + date.hashCode()
        return result
    }

    override fun compareTo(other: Date): Int = this.date.compare(other.date).toInt()

    private fun updateDateForComponent(component: NSCalendarUnit, value: Int) {
        // Check whether this component update can use dateBySettingUnit.
        // This doesn't work properly when going out of bounds for a component (e.g. setting hour to 26)
        // Therefore we must ensure that the value is within bounds.
        // This is especially important for Minutes and Hours due to daylight savings,
        // as using dateByAddingUnit will be incorrect if changing between a date that has DLS and one that hasn't.
        val canSetComponent = when (component) {
            NSCalendarUnitMinute -> {
                calendar.rangeOfUnit(NSCalendarUnitMinute, NSCalendarUnitHour, date).useContents {
                    value >= location.toInt() && value < (location + length).toInt()
                }
            }
            NSCalendarUnitHour -> {
                calendar.rangeOfUnit(NSCalendarUnitHour, NSCalendarUnitDay, date).useContents {
                    value >= location.toInt() && value < (location + length).toInt()
                }
            }
            else -> {
                false
            }
        }
        if (canSetComponent) {
            // If the new value is lower than the old one, make sure we go backwards as otherwise the next highest component will increase
            val calendarOptions = if (value.toLong() < calendar.component(component, date))
                NSCalendarMatchPreviousTimePreservingSmallerUnits or NSCalendarSearchBackwards
            else
                NSCalendarMatchNextTimePreservingSmallerUnits
            calendar.dateBySettingUnit(component, value.toLong() as NSInteger, date, calendarOptions)
        } else {
            val previousValue = calendar.component(component, this.date)
            calendar.dateByAddingUnit(
                component,
                (value - previousValue).toLong() as NSInteger,
                date,
                0UL as NSCalendarOptions
            )
        }?.let {
            date = it
        }
    }

    override fun toString(): String {
        return date.toString()
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

import platform.Foundation.NSDecimalNumber
import platform.Foundation.NSDecimalNumberHandler
import platform.Foundation.NSRoundingMode
import platform.darwin.NSUInteger
import kotlin.math.absoluteValue

actual data class Decimal(val nsDecimal: NSDecimalNumber) : Comparable<Decimal> {
    override fun compareTo(other: Decimal): Int = nsDecimal.compare(other.nsDecimal).toInt()
    override fun equals(other: Any?): Boolean = (other as? Decimal)?.let { nsDecimal.isEqualToNumber(it.nsDecimal) } ?: false
    override fun hashCode(): Int {
        return nsDecimal.hashCode()
    }
}

actual operator fun Decimal.plus(value: Decimal) = copy(nsDecimal = nsDecimal.decimalNumberByAdding(value.nsDecimal))

actual fun Decimal.plus(value: Decimal, scale: Int) = copy(
    nsDecimal = nsDecimal.decimalNumberByAdding(
        decimalNumber = value.nsDecimal,
        withBehavior = NSDecimalNumberHandler(
            roundingMode = NSRoundingMode.NSRoundPlain,
            scale = scale.toShort(),
            raiseOnExactness = false,
            raiseOnOverflow = false,
            raiseOnUnderflow = false,
            raiseOnDivideByZero = true
        )
    )
)

actual fun Decimal.plus(value: Decimal, scale: Int, roundingMode: RoundingMode) = copy(
    nsDecimal = nsDecimal.decimalNumberByAdding(
        decimalNumber = value.nsDecimal,
        withBehavior = NSDecimalNumberHandler(
            roundingMode = roundingMode.nsRoundingMode,
            scale = scale.toShort(),
            raiseOnExactness = false,
            raiseOnOverflow = false,
            raiseOnUnderflow = false,
            raiseOnDivideByZero = true
        )
    )
)

actual operator fun Decimal.minus(value: Decimal) = copy(nsDecimal = nsDecimal.decimalNumberBySubtracting(value.nsDecimal))

actual fun Decimal.minus(value: Decimal, scale: Int) = copy(
    nsDecimal = nsDecimal.decimalNumberBySubtracting(
        decimalNumber = value.nsDecimal,
        withBehavior = NSDecimalNumberHandler(
            roundingMode = NSRoundingMode.NSRoundPlain,
            scale = scale.toShort(),
            raiseOnExactness = false,
            raiseOnOverflow = false,
            raiseOnUnderflow = false,
            raiseOnDivideByZero = true
        )
    )
)

actual fun Decimal.minus(value: Decimal, scale: Int, roundingMode: RoundingMode) = copy(
    nsDecimal = nsDecimal.decimalNumberBySubtracting(
        decimalNumber = value.nsDecimal,
        withBehavior = NSDecimalNumberHandler(
            roundingMode = roundingMode.nsRoundingMode,
            scale = scale.toShort(),
            raiseOnExactness = false,
            raiseOnOverflow = false,
            raiseOnUnderflow = false,
            raiseOnDivideByZero = true
        )
    )
)

actual operator fun Decimal.div(value: Decimal) = copy(nsDecimal = nsDecimal.decimalNumberByDividingBy(value.nsDecimal))

actual fun Decimal.div(value: Decimal, scale: Int) = copy(
    nsDecimal = nsDecimal.decimalNumberByDividingBy(
        decimalNumber = value.nsDecimal,
        withBehavior = NSDecimalNumberHandler(
            NSRoundingMode.NSRoundBankers,
            scale.toShort(),
            raiseOnExactness = true,
            raiseOnOverflow = true,
            raiseOnUnderflow = true,
            raiseOnDivideByZero = true
        )
    )
)

actual fun Decimal.div(value: Decimal, scale: Int, roundingMode: RoundingMode) = copy(
    nsDecimal = nsDecimal.decimalNumberByDividingBy(
        decimalNumber = value.nsDecimal,
        withBehavior = NSDecimalNumberHandler(
            roundingMode = roundingMode.nsRoundingMode,
            scale = scale.toShort(),
            raiseOnExactness = false,
            raiseOnOverflow = false,
            raiseOnUnderflow = false,
            raiseOnDivideByZero = true
        )
    )
)

actual operator fun Decimal.times(value: Decimal) = copy(nsDecimal = nsDecimal.decimalNumberByMultiplyingBy(value.nsDecimal))

actual fun Decimal.times(value: Decimal, scale: Int) = copy(
    nsDecimal = nsDecimal.decimalNumberByMultiplyingBy(
        decimalNumber = value.nsDecimal,
        NSDecimalNumberHandler(
            roundingMode = NSRoundingMode.NSRoundPlain,
            scale = scale.toShort(),
            raiseOnExactness = false,
            raiseOnOverflow = false,
            raiseOnUnderflow = false,
            raiseOnDivideByZero = true
        )
    )
)

actual fun Decimal.times(value: Decimal, scale: Int, roundingMode: RoundingMode) = copy(
    nsDecimal = nsDecimal.decimalNumberByMultiplyingBy(
        decimalNumber = value.nsDecimal,
        withBehavior = NSDecimalNumberHandler(
            roundingMode = roundingMode.nsRoundingMode,
            scale = scale.toShort(),
            raiseOnExactness = false,
            raiseOnOverflow = false,
            raiseOnUnderflow = false,
            raiseOnDivideByZero = true
        )
    )
)

actual infix fun Decimal.pow(n: Int): Decimal = if (n < 0)
    1.toDecimal() / pow(n.absoluteValue)
else
    copy(nsDecimal = nsDecimal.decimalNumberByRaisingToPower(n.toULong() as NSUInteger))

actual fun Decimal.pow(n: Int, scale: Int): Decimal = if (n < 0)
    1.toDecimal() / pow(n.absoluteValue, scale)
else
    copy(
        nsDecimal = nsDecimal.decimalNumberByRaisingToPower(
            n.toULong() as NSUInteger,
            NSDecimalNumberHandler(
                roundingMode = NSRoundingMode.NSRoundPlain,
                scale = scale.toShort(),
                raiseOnExactness = false,
                raiseOnOverflow = false,
                raiseOnUnderflow = false,
                raiseOnDivideByZero = true
            )
        )
    )
actual fun Decimal.pow(
    n: Int,
    scale: Int,
    roundingMode: RoundingMode
): Decimal = if (n < 0)
    1.toDecimal() / pow(n.absoluteValue, scale, roundingMode)
else
    copy(
        nsDecimal = nsDecimal.decimalNumberByRaisingToPower(
            n.toULong() as NSUInteger,
            withBehavior = NSDecimalNumberHandler(
                roundingMode = roundingMode.nsRoundingMode,
                scale = scale.toShort(),
                raiseOnExactness = false,
                raiseOnOverflow = false,
                raiseOnUnderflow = false,
                raiseOnDivideByZero = true
            )
        )
    )

actual fun Number.toDecimal() = Decimal(NSDecimalNumber(this.toString()))
actual fun String.toDecimal() = Decimal(NSDecimalNumber(this))

actual fun Decimal.toDouble() = nsDecimal.toString().toDouble()
actual fun Decimal.toInt() = nsDecimal.intValue
actual fun Decimal.toString() = nsDecimal.stringValue

actual fun Decimal.round(scale: Int, roundingMode: RoundingMode) = copy(
    nsDecimal = nsDecimal.decimalNumberByRoundingAccordingToBehavior(
        NSDecimalNumberHandler(
            roundingMode = roundingMode.nsRoundingMode,
            scale = scale.toShort(),
            raiseOnExactness = false,
            raiseOnOverflow = false,
            raiseOnUnderflow = false,
            raiseOnDivideByZero = true
        )
    )
)

val RoundingMode.nsRoundingMode
    get() = when (this) {
        RoundingMode.RoundDown -> NSRoundingMode.NSRoundDown
        RoundingMode.RoundHalfEven -> NSRoundingMode.NSRoundBankers
        RoundingMode.RoundUp -> NSRoundingMode.NSRoundUp
    }

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

import com.splendo.kaluga.base.typedList
import platform.Foundation.NSLocale
import platform.Foundation.alternateQuotationBeginDelimiter
import platform.Foundation.alternateQuotationEndDelimiter
import platform.Foundation.availableLocaleIdentifiers
import platform.Foundation.countryCode
import platform.Foundation.currentLocale
import platform.Foundation.languageCode
import platform.Foundation.localeIdentifier
import platform.Foundation.localizedStringForCountryCode
import platform.Foundation.localizedStringForLanguageCode
import platform.Foundation.localizedStringForLocaleIdentifier
import platform.Foundation.localizedStringForScriptCode
import platform.Foundation.localizedStringForVariantCode
import platform.Foundation.quotationBeginDelimiter
import platform.Foundation.quotationEndDelimiter
import platform.Foundation.scriptCode
import platform.Foundation.variantCode

actual class Locale internal constructor(val nsLocale: NSLocale) {

    actual companion object {
        actual fun createLocale(language: String): Locale = Locale(NSLocale(language))
        actual fun createLocale(language: String, country: String): Locale = Locale(NSLocale("${language}_$country"))
        actual fun createLocale(language: String, country: String, variant: String): Locale = Locale(NSLocale("${language}_${country}_$variant"))

        actual val defaultLocale: Locale get() = Locale(NSLocale.currentLocale)
        actual val availableLocales: List<Locale> = NSLocale.availableLocaleIdentifiers.typedList<String>().map { Locale(NSLocale(it)) }
    }

    actual val countryCode: String
        get() = nsLocale.countryCode ?: ""
    actual val languageCode: String
        get() = nsLocale.languageCode
    actual val scriptCode: String
        get() = nsLocale.scriptCode ?: ""
    actual val variantCode: String
        get() = nsLocale.variantCode ?: ""
    actual val unitSystem: UnitSystem
        get() = (nsLocale.objectForKey("kCFLocaleMeasurementSystemKey") as? String)?.let {
            UnitSystem.withRawValue(it)
        } ?: UnitSystem.METRIC

    actual fun name(forLocale: Locale): String = forLocale.nsLocale.localizedStringForLocaleIdentifier(nsLocale.localeIdentifier)
    actual fun countryName(forLocale: Locale): String = forLocale.nsLocale.localizedStringForCountryCode(countryCode) ?: ""
    actual fun languageName(forLocale: Locale): String = forLocale.nsLocale.localizedStringForLanguageCode(languageCode) ?: ""
    actual fun variantName(forLocale: Locale): String = forLocale.nsLocale.localizedStringForVariantCode(variantCode) ?: ""
    actual fun scriptName(forLocale: Locale): String = forLocale.nsLocale.localizedStringForScriptCode(scriptCode) ?: ""

    actual val quotationStart: String = nsLocale.quotationBeginDelimiter
    actual val quotationEnd: String = nsLocale.quotationEndDelimiter
    actual val alternateQuotationStart: String = nsLocale.alternateQuotationBeginDelimiter
    actual val alternateQuotationEnd: String = nsLocale.alternateQuotationEndDelimiter
}

package com.splendo.kaluga.base.utils

import kotlinx.cinterop.CValue
import kotlinx.cinterop.convert
import kotlinx.cinterop.useContents
import platform.Foundation.NSMakeRange
import platform.Foundation.NSRange

val IntRange.nsRange: CValue<NSRange> get() = NSMakeRange(start.convert(), (endInclusive + 1 - start).convert())
val CValue<NSRange>.range: IntRange get() = useContents { IntRange(location.toInt(), (location + length).toInt() - 1) }

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

actual interface Serializable

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

import com.splendo.kaluga.base.typedList
import com.splendo.kaluga.base.typedMap
import platform.Foundation.NSTimeZone
import platform.Foundation.NSTimeZoneNameStyle
import platform.Foundation.abbreviationDictionary
import platform.Foundation.daylightSavingTimeOffset
import platform.Foundation.defaultTimeZone
import platform.Foundation.isDaylightSavingTime
import platform.Foundation.knownTimeZoneNames
import platform.Foundation.localizedName
import platform.Foundation.nextDaylightSavingTimeTransition
import platform.Foundation.secondsFromGMT
import platform.Foundation.timeZoneWithAbbreviation
import platform.Foundation.timeZoneWithName

actual class TimeZone internal constructor(val timeZone: NSTimeZone) {

    actual companion object {
        actual fun get(identifier: String): TimeZone? {
            return if (NSTimeZone.abbreviationDictionary.typedMap<String, String>().containsKey(identifier)) {
                NSTimeZone.timeZoneWithAbbreviation(identifier)
            } else {
                NSTimeZone.timeZoneWithName(identifier)
            }?.let {
                TimeZone(it)
            }
        }
        actual fun current(): TimeZone = TimeZone(NSTimeZone.defaultTimeZone)
        actual val availableIdentifiers: List<String> = NSTimeZone.knownTimeZoneNames.typedList()
    }

    actual val identifier: String = timeZone.name
    actual fun displayName(style: TimeZoneNameStyle, withDaylightSavings: Boolean, locale: Locale): String {
        val nameStyle = when (style) {
            TimeZoneNameStyle.Short -> if (withDaylightSavings) NSTimeZoneNameStyle.NSTimeZoneNameStyleShortDaylightSaving else NSTimeZoneNameStyle.NSTimeZoneNameStyleShortStandard
            TimeZoneNameStyle.Long -> if (withDaylightSavings) NSTimeZoneNameStyle.NSTimeZoneNameStyleDaylightSaving else NSTimeZoneNameStyle.NSTimeZoneNameStyleStandard
        }
        return timeZone.localizedName(nameStyle, locale.nsLocale) ?: ""
    }
    actual val offsetFromGMTInMilliseconds: Long get() {
        val rawOffset = if (timeZone.isDaylightSavingTime())
            timeZone.secondsFromGMT.toDouble() - timeZone.daylightSavingTimeOffset
        else
            timeZone.secondsFromGMT.toDouble()

        return rawOffset.toLong() * 1000L
    }

    actual val daylightSavingsOffsetInMilliseconds: Long get() {
        val rawOffset = if (timeZone.isDaylightSavingTime()) {
            timeZone.daylightSavingTimeOffset.toLong()
        } else {
            timeZone.nextDaylightSavingTimeTransition?.let {
                timeZone.daylightSavingTimeOffsetForDate(it).toLong()
            } ?: 0L
        }
        return rawOffset * 1000L
    }
    actual fun offsetFromGMTAtDateInMilliseconds(date: Date): Long = (timeZone.secondsFromGMTForDate(date.date) * 1000L)
    actual fun usesDaylightSavingsTime(date: Date): Boolean = timeZone.isDaylightSavingTimeForDate(date.date)
    actual fun copy(): TimeZone = TimeZone(timeZone.copy() as NSTimeZone)
    override fun equals(other: Any?): Boolean {
        return (other as? TimeZone)?.let { timeZone == other.timeZone } ?: false
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Date
import com.splendo.kaluga.base.utils.Locale
import com.splendo.kaluga.base.utils.TimeZone

// TODO Implement with proper dateformatter solution for Java Script
actual class DateFormatter private constructor(initialTimeZone: TimeZone, private val formatter: (kotlin.js.Date) -> String) {

    actual companion object {
        actual fun dateFormat(
            style: DateFormatStyle,
            timeZone: TimeZone,
            locale: Locale
        ): DateFormatter = DateFormatter(timeZone) { date -> date.toLocaleDateString(arrayOf("${locale.languageCode}-${locale.countryCode}")) }

        actual fun timeFormat(
            style: DateFormatStyle,
            timeZone: TimeZone,
            locale: Locale
        ): DateFormatter = DateFormatter(timeZone) { date -> date.toLocaleTimeString(arrayOf("${locale.languageCode}-${locale.countryCode}")) }

        actual fun dateTimeFormat(
            dateStyle: DateFormatStyle,
            timeStyle: DateFormatStyle,
            timeZone: TimeZone,
            locale: Locale
        ): DateFormatter = DateFormatter(timeZone) { date -> date.toLocaleString(arrayOf("${locale.languageCode}-${locale.countryCode}")) }

        actual fun patternFormat(pattern: String, timeZone: TimeZone, locale: Locale): DateFormatter = DateFormatter(timeZone) { date -> date.toLocaleString(arrayOf("${locale.languageCode}-${locale.countryCode}")) }
    }

    actual var pattern: String = ""

    actual var timeZone: TimeZone = initialTimeZone
    actual var eras: List<String> = emptyList()

    actual var months: List<String> = emptyList()
    actual var shortMonths: List<String> = emptyList()

    actual var weekdays: List<String> = emptyList()
    actual var shortWeekdays: List<String> = emptyList()

    actual var amString: String = ""
    actual var pmString: String = ""

    actual fun format(date: Date): String = formatter(date.date)
    actual fun parse(string: String): Date? = null
}

private fun DateFormatStyle.stringValue(): String = when (this) {
    DateFormatStyle.Short -> "short"
    DateFormatStyle.Medium -> "medium"
    DateFormatStyle.Long -> "long"
    DateFormatStyle.Full -> "full"
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Locale

// TODO Implement with proper number formatter solution for Java Script
actual class NumberFormatter actual constructor(actual val locale: Locale, style: NumberFormatStyle) {
    actual var percentSymbol: Char = Char.MIN_VALUE
    actual var perMillSymbol: Char = Char.MIN_VALUE
    actual var minusSign: Char = Char.MIN_VALUE
    actual var exponentSymbol: String = ""
    actual var zeroSymbol: Char = Char.MIN_VALUE
    actual var notANumberSymbol: String = ""
    actual var infinitySymbol: String = ""
    actual var currencySymbol: String = ""
    actual var currencyCode: String = ""
    actual var positivePrefix: String = ""
    actual var positiveSuffix: String = ""
    actual var negativePrefix: String = ""
    actual var negativeSuffix: String = ""
    actual var groupingSeparator: Char = Char.MIN_VALUE
    actual var usesGroupingSeparator: Boolean = false
    actual var decimalSeparator: Char = Char.MIN_VALUE
    actual var alwaysShowsDecimalSeparator: Boolean = false
    actual var currencyDecimalSeparator: Char = Char.MIN_VALUE
    actual var groupingSize: Int = 0
    actual var multiplier: Int = 1

    actual fun format(number: Number): String = ""
    actual fun parse(string: String): Number? = null
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Locale

actual val lineSeparator = "\n"

actual fun String.lowerCased(locale: Locale): String = this.lowercase()

actual fun String.upperCased(locale: Locale): String = this.uppercase()

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.

 */
@file:JsModule("@splendo/bigdecimal")
@file:JsNonModule
package com.splendo.kaluga.base.utils

external class BigDecimal(a: Any, b: Any) {
    var significand: Any
    var exponent: Any

    companion object {
        fun BigDecimal(a: Any): BigDecimal
        fun add(a: BigDecimal, b: BigDecimal, rounding: Rounding? = definedExternally): BigDecimal
        fun subtract(a: BigDecimal, b: BigDecimal, rounding: Rounding? = definedExternally): BigDecimal
        fun divide(a: BigDecimal, b: BigDecimal, rounding: Rounding? = definedExternally): BigDecimal
        fun multiply(a: BigDecimal, b: BigDecimal, rounding: Rounding? = definedExternally): BigDecimal
        fun round(a: BigDecimal, rounding: Rounding? = definedExternally): BigDecimal
        fun toNumber(a: BigDecimal): Double
        fun greaterThan(a: BigDecimal, b: BigDecimal): Boolean
        fun lessThan(a: BigDecimal, b: BigDecimal): Boolean
        fun compare(a: BigDecimal, b: BigDecimal): Int
        fun equal(a: BigDecimal, b: BigDecimal): Boolean
    }

    fun toFixed(fractionDigits: Int, roundingMode: String = definedExternally): Int
    override fun toString(): String
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

// TODO Implement with proper date solution for Java Script
actual class Date internal constructor(internal val date: kotlin.js.Date) : Comparable<Date> {

    actual companion object {
        actual fun now(offsetInMilliseconds: Long, timeZone: TimeZone, locale: Locale): Date = Date(kotlin.js.Date(kotlin.js.Date.now() + offsetInMilliseconds))
        actual fun epoch(offsetInMilliseconds: Long, timeZone: TimeZone, locale: Locale): Date = Date(kotlin.js.Date(offsetInMilliseconds))
    }

    actual var timeZone: TimeZone
        get() = TimeZone()
        set(value) { }

    actual var era: Int
        get() = 0
        set(value) { }
    actual var year: Int
        get() = date.getFullYear()
        set(value) { }
    actual var month: Int
        get() = date.getMonth()
        set(value) { }
    actual val daysInMonth: Int = 0
    actual var weekOfYear: Int
        get() = 0
        set(value) { }
    actual var weekOfMonth: Int
        get() = 0
        set(value) { }
    actual var day: Int
        get() = 0
        set(value) { }
    actual var dayOfYear: Int
        get() = date.getDay()
        set(value) { }
    actual var weekDay: Int
        get() = date.getDate() + 1
        set(value) { }
    actual var firstWeekDay: Int
        get() = 1
        set(value) { }

    actual var hour: Int
        get() = date.getHours()
        set(value) { }
    actual var minute: Int
        get() = date.getMinutes()
        set(value) { }
    actual var second: Int
        get() = date.getSeconds()
        set(value) { }
    actual var millisecond: Int
        get() = date.getMilliseconds()
        set(value) { }
    actual var millisecondSinceEpoch: Long
        get() = date.getTime().toLong()
        set(value) { }

    actual fun copy(): Date = Date(kotlin.js.Date(date.getMilliseconds()))

    actual override fun equals(other: Any?): Boolean {
        return (other as? Date)?.let {
            timeZone == other.timeZone && millisecondSinceEpoch == other.millisecondSinceEpoch
        } ?: false
    }

    override fun compareTo(other: Date): Int {
        return when {
            date.getMilliseconds() < other.millisecond -> -1
            date.getMilliseconds() == other.millisecond -> 0
            else -> 1
        }
    }

    actual override fun hashCode(): Int {
        return date.hashCode()
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

import com.splendo.kaluga.base.utils.RoundingMode.RoundDown
import com.splendo.kaluga.base.utils.RoundingMode.RoundHalfEven
import com.splendo.kaluga.base.utils.RoundingMode.RoundUp
import kotlin.math.pow

// 34 digits of the IEEE 754R Decimal128 format
val DECIMAL_128_SIGNIFICANT_DIGITS = 34
// Max decimal digits + 1 for intermediate division
val DIV_DECIMAL_128_SIGNIFICANT_DIGITS = DECIMAL_128_SIGNIFICANT_DIGITS + 1

// Default is half-even
val ROUNDING_MODE = "half-even"
val CEIL = "ceil"
val FLOOR = "floor"

data class Rounding(
    val maximumFractionDigits: Int? = null,
    val roundingMode: String = ROUNDING_MODE,
    val maximumSignificantDigits: Int = DECIMAL_128_SIGNIFICANT_DIGITS
)

actual data class Decimal(val bd: BigDecimal) : Comparable<Decimal> {
    override fun compareTo(other: Decimal): Int = BigDecimal.compare(bd, other.bd)
    override fun equals(other: Any?): Boolean = (other as? Decimal)?.let { BigDecimal.equal(bd, it.bd) } ?: false
    override fun hashCode(): Int {
        return bd.hashCode()
    }
}

val ZERO = BigDecimal.BigDecimal(0)

fun round(
    a: BigDecimal,
    roundingMode: RoundingMode,
    maximumFractionDigits: Int? = null
): Decimal {
    val mode = if (BigDecimal.lessThan(a, ZERO)) {
        when (roundingMode) {
            RoundDown -> CEIL
            RoundHalfEven -> ROUNDING_MODE
            RoundUp -> FLOOR
        }
    } else {
        when (roundingMode) {
            RoundDown -> FLOOR
            RoundHalfEven -> ROUNDING_MODE
            RoundUp -> CEIL
        }
    }
    return Decimal(BigDecimal.round(a, Rounding(maximumFractionDigits, mode)))
}

actual operator fun Decimal.plus(value: Decimal): Decimal = Decimal(BigDecimal.add(bd, value.bd))

actual fun Decimal.plus(
    value: Decimal,
    scale: Int
): Decimal = Decimal(BigDecimal.add(bd, value.bd, Rounding(scale)))

actual fun Decimal.plus(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode
): Decimal = round(BigDecimal.add(bd, value.bd), roundingMode, scale)

actual operator fun Decimal.minus(value: Decimal): Decimal = Decimal(BigDecimal.subtract(bd, value.bd))

actual fun Decimal.minus(
    value: Decimal,
    scale: Int
): Decimal = Decimal(BigDecimal.subtract(bd, value.bd, Rounding(scale)))

actual fun Decimal.minus(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode
): Decimal = round(BigDecimal.subtract(bd, value.bd), roundingMode, scale)

actual operator fun Decimal.div(
    value: Decimal
): Decimal = Decimal(BigDecimal.divide(bd, value.bd, Rounding()))

actual fun Decimal.div(
    value: Decimal,
    scale: Int
): Decimal = Decimal(BigDecimal.divide(bd, value.bd, Rounding(scale)))

actual fun Decimal.div(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode
): Decimal = round(BigDecimal.divide(bd, value.bd, Rounding(scale + 1, ROUNDING_MODE, DIV_DECIMAL_128_SIGNIFICANT_DIGITS)), roundingMode, scale)

actual operator fun Decimal.times(
    value: Decimal
): Decimal = Decimal(BigDecimal.multiply(bd, value.bd, Rounding()))

actual fun Decimal.times(
    value: Decimal,
    scale: Int
): Decimal = Decimal(BigDecimal.multiply(bd, value.bd, Rounding(scale)))

actual fun Decimal.times(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode
): Decimal = round(BigDecimal.multiply(bd, value.bd), roundingMode, scale)

actual infix fun Decimal.pow(n: Int): Decimal = Decimal(BigDecimal.BigDecimal(toDouble().pow(n)))
actual fun Decimal.pow(n: Int, scale: Int): Decimal = Decimal(BigDecimal.round((this pow n).bd, Rounding(scale)))
actual fun Decimal.pow(
    n: Int,
    scale: Int,
    roundingMode: RoundingMode
): Decimal = this pow n

actual fun Decimal.round(
    scale: Int,
    roundingMode: RoundingMode
): Decimal = round(bd, roundingMode, scale)

actual fun Number.toDecimal(): Decimal = this.toString().toDecimal()
actual fun String.toDecimal(): Decimal = Decimal(BigDecimal.BigDecimal(this))

actual fun Decimal.toDouble(): Double = bd.toString().toDouble()
actual fun Decimal.toInt(): Int = bd.toFixed(0).toInt()
actual fun Decimal.toString(): String = bd.toString()

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

// TODO Implement with proper locale solution for Java Script
actual class Locale internal constructor(
    actual val languageCode: String,
    actual val countryCode: String = "",
    actual val variantCode: String = "",
    actual val scriptCode: String = "",
    actual val unitSystem: UnitSystem = UnitSystem.METRIC
) {

    actual companion object {
        actual fun createLocale(language: String): Locale = Locale(language)
        actual fun createLocale(language: String, country: String): Locale = Locale(language, country)
        actual fun createLocale(language: String, country: String, variant: String): Locale = Locale(language, country, variant)

        actual val defaultLocale: Locale get() = Locale("en")
        actual val availableLocales: List<Locale> = emptyList()
    }

    actual fun name(forLocale: Locale): String = "${languageCode}_${countryCode}_$variantCode"
    actual fun countryName(forLocale: Locale): String = countryCode
    actual fun languageName(forLocale: Locale): String = languageCode
    actual fun variantName(forLocale: Locale): String = variantCode
    actual fun scriptName(forLocale: Locale): String = scriptCode

    actual val quotationStart: String = "\""
    actual val quotationEnd: String = "\""
    actual val alternateQuotationStart: String = "\""
    actual val alternateQuotationEnd: String = "\""
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

actual interface Serializable

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

// TODO Implement with proper timezone solution for Java Script
actual class TimeZone internal constructor() {

    actual companion object {
        actual fun get(identifier: String): TimeZone? = TimeZone()
        actual fun current(): TimeZone = TimeZone()
        actual val availableIdentifiers: List<String> = emptyList()
    }

    actual val identifier: String = ""
    actual fun displayName(style: TimeZoneNameStyle, withDaylightSavings: Boolean, locale: Locale): String = ""
    actual val offsetFromGMTInMilliseconds = 0L
    actual val daylightSavingsOffsetInMilliseconds: Long = 0L
    actual fun offsetFromGMTAtDateInMilliseconds(date: Date): Long = 0L
    actual fun usesDaylightSavingsTime(date: Date): Boolean = false
    actual fun copy(): TimeZone = TimeZone()
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.

 */

@file:JvmName("JvmDateFormatter")
package com.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Date
import com.splendo.kaluga.base.utils.Locale
import com.splendo.kaluga.base.utils.TimeZone
import java.text.DateFormat
import java.text.DateFormatSymbols
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar

actual class DateFormatter private constructor(private val format: SimpleDateFormat) {

    actual companion object {
        actual fun dateFormat(
            style: DateFormatStyle,
            timeZone: TimeZone,
            locale: Locale
        ): DateFormatter = createDateFormatter(DateFormat.getDateInstance(style.javaStyle(), locale.locale) as SimpleDateFormat, timeZone)

        actual fun timeFormat(
            style: DateFormatStyle,
            timeZone: TimeZone,
            locale: Locale
        ): DateFormatter = createDateFormatter(DateFormat.getTimeInstance(style.javaStyle(), locale.locale) as SimpleDateFormat, timeZone)

        actual fun dateTimeFormat(
            dateStyle: DateFormatStyle,
            timeStyle: DateFormatStyle,
            timeZone: TimeZone,
            locale: Locale
        ): DateFormatter = createDateFormatter(DateFormat.getDateTimeInstance(dateStyle.javaStyle(), timeStyle.javaStyle(), locale.locale) as SimpleDateFormat, timeZone)

        actual fun patternFormat(
            pattern: String,
            timeZone: TimeZone,
            locale: Locale
        ): DateFormatter = createDateFormatter(SimpleDateFormat(pattern, locale.locale), timeZone)

        fun createDateFormatter(simpleDateFormat: SimpleDateFormat, timeZone: TimeZone): DateFormatter {
            return DateFormatter(simpleDateFormat).apply {
                this.timeZone = timeZone
            }
        }
    }

    private val symbols: DateFormatSymbols get() = format.dateFormatSymbols

    actual var pattern: String
        get() = format.toPattern()
        set(value) = format.applyPattern(value)

    actual var timeZone: TimeZone
        get() = TimeZone(format.timeZone)
        set(value) { format.timeZone = value.timeZone }

    actual var eras: List<String>
        get() = symbols.eras.toList()
        set(value) { updateSymbols { it.eras = value.toTypedArray() } }

    actual var months: List<String>
        get() = symbols.months.toList()
        set(value) { updateSymbols { it.months = value.toTypedArray() } }
    actual var shortMonths: List<String>
        get() = symbols.shortMonths.toList()
        set(value) { updateSymbols { it.shortMonths = value.toTypedArray() } }

    actual var weekdays: List<String>
        get() {
            val weekdaysWithEmptyFirst = symbols.weekdays.toList()
            return if (weekdaysWithEmptyFirst.size > 1) {
                weekdaysWithEmptyFirst.subList(1, weekdaysWithEmptyFirst.size)
            } else {
                emptyList()
            }
        }
        set(value) {
            updateSymbols {
                val weekdaysWithEmptyFirst = value.toMutableList().apply {
                    add(0, "")
                }
                it.weekdays = weekdaysWithEmptyFirst.toTypedArray()
            }
        }
    actual var shortWeekdays: List<String>
        get() {
            val weekdaysWithEmptyFirst = symbols.shortWeekdays.toList()
            return if (weekdaysWithEmptyFirst.size > 1) {
                weekdaysWithEmptyFirst.subList(1, weekdaysWithEmptyFirst.size)
            } else {
                emptyList()
            }
        }
        set(value) {
            updateSymbols {
                val weekdaysWithEmptyFirst = value.toMutableList().apply {
                    add(0, "")
                }
                it.shortWeekdays = weekdaysWithEmptyFirst.toTypedArray()
            }
        }

    actual var amString: String
        get() = symbols.amPmStrings.toList()[0]
        set(value) { updateSymbols { it.amPmStrings = it.amPmStrings.toMutableList().apply { this[0] = value }.toTypedArray() } }
    actual var pmString: String
        get() = symbols.amPmStrings.toList()[1]
        set(value) { updateSymbols { it.amPmStrings = it.amPmStrings.toMutableList().apply { this[1] = value }.toTypedArray() } }

    actual fun format(date: Date): String = format.format(date.calendar.time)
    actual fun parse(string: String): Date? {
        return try {
            format.parse(string)?.let { date ->
                val calendar = format.calendar.clone() as Calendar
                Date(calendar.apply { time = date })
            }
        } catch (e: ParseException) {
            null
        }
    }

    private fun updateSymbols(transform: (DateFormatSymbols) -> Unit) {
        val symbols = this.symbols
        transform(symbols)
        format.dateFormatSymbols = symbols
    }
}

private fun DateFormatStyle.javaStyle(): Int = when (this) {
    DateFormatStyle.Short -> DateFormat.SHORT
    DateFormatStyle.Medium -> DateFormat.MEDIUM
    DateFormatStyle.Long -> DateFormat.LONG
    DateFormatStyle.Full -> DateFormat.FULL
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Locale
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.text.ParseException
import java.util.Currency

@ExperimentalUnsignedTypes
actual class NumberFormatter actual constructor(actual val locale: Locale, style: NumberFormatStyle) {

    private val format: DecimalFormat = when (style) {
        is NumberFormatStyle.Integer -> DecimalFormat.getInstance(locale.locale).apply {
            minimumIntegerDigits = style.minDigits.toInt()
            maximumIntegerDigits = style.maxDigits.toInt()
            minimumFractionDigits = 0
            maximumFractionDigits = 0
        }
        is NumberFormatStyle.Decimal -> DecimalFormat.getInstance(locale.locale).apply {
            minimumIntegerDigits = style.minIntegerDigits.toInt()
            maximumIntegerDigits = style.maxIntegerDigits.toInt()
            minimumFractionDigits = style.minFractionDigits.toInt()
            maximumFractionDigits = style.maxFractionDigits.toInt()
        }
        is NumberFormatStyle.Percentage -> DecimalFormat.getPercentInstance(locale.locale).apply {
            minimumIntegerDigits = style.minIntegerDigits.toInt()
            maximumIntegerDigits = style.maxIntegerDigits.toInt()
            minimumFractionDigits = style.minFractionDigits.toInt()
            maximumFractionDigits = style.maxFractionDigits.toInt()
        }
        is NumberFormatStyle.Permillage -> {
            val pattern = (DecimalFormat.getPercentInstance(locale.locale) as DecimalFormat).toPattern().replace("%", "\u2030")
            DecimalFormat(pattern, DecimalFormatSymbols(locale.locale)).apply {
                minimumIntegerDigits = style.minIntegerDigits.toInt()
                maximumIntegerDigits = style.maxIntegerDigits.toInt()
                minimumFractionDigits = style.minFractionDigits.toInt()
                maximumFractionDigits = style.maxFractionDigits.toInt()
            }
        }
        is NumberFormatStyle.Scientific -> DecimalFormat(style.pattern, DecimalFormatSymbols(locale.locale))
        is NumberFormatStyle.Currency -> DecimalFormat.getCurrencyInstance(locale.locale).apply {
            style.currencyCode?.let { currencyCode ->
                currency = Currency.getInstance(currencyCode)
            }
            minimumIntegerDigits = style.minIntegerDigits.toInt()
            maximumIntegerDigits = style.maxIntegerDigits.toInt()
            minimumFractionDigits = style.minFractionDigits?.toInt() ?: currency?.defaultFractionDigits ?: 0
            maximumFractionDigits = style.maxFractionDigits?.toInt() ?: currency?.defaultFractionDigits ?: 0
        }
        is NumberFormatStyle.Pattern -> DecimalFormat("${style.positivePattern};${style.negativePattern}", DecimalFormatSymbols(locale.locale))
    } as DecimalFormat
    private val symbols: DecimalFormatSymbols get() = format.decimalFormatSymbols
    private fun applySymbols(apply: (DecimalFormatSymbols) -> Unit) {
        val symbols = format.decimalFormatSymbols
        apply(symbols)
        format.decimalFormatSymbols = symbols
    }

    init {
        format.roundingMode = when (style.roundingMode) {
            RoundingMode.Ceiling -> java.math.RoundingMode.CEILING
            RoundingMode.Floor -> java.math.RoundingMode.FLOOR
            RoundingMode.HalfEven -> java.math.RoundingMode.HALF_EVEN
            RoundingMode.HalfUp -> java.math.RoundingMode.HALF_UP
            RoundingMode.HalfDown -> java.math.RoundingMode.HALF_DOWN
            RoundingMode.Down -> java.math.RoundingMode.DOWN
            RoundingMode.Up -> java.math.RoundingMode.UP
        }
    }

    actual var percentSymbol: Char
        get() = symbols.percent
        set(value) { applySymbols { it.percent = value } }
    actual var perMillSymbol: Char
        get() = symbols.perMill
        set(value) { applySymbols { it.perMill = value } }
    actual var minusSign: Char
        get() = symbols.minusSign
        set(value) { applySymbols { it.minusSign = value } }
    actual var exponentSymbol: String
        get() = symbols.exponentSeparator
        set(value) { applySymbols { it.exponentSeparator = value } }
    actual var zeroSymbol: Char
        get() = symbols.zeroDigit
        set(value) { applySymbols { it.zeroDigit = value } }
    actual var notANumberSymbol: String
        get() = symbols.naN
        set(value) { applySymbols { it.naN = value } }
    actual var infinitySymbol: String
        get() = symbols.infinity
        set(value) { applySymbols { it.infinity = value } }

    actual var currencySymbol: String
        get() = symbols.currencySymbol
        set(value) { applySymbols { it.currencySymbol = value } }
    actual var currencyCode: String
        get() = symbols.currency.currencyCode
        set(value) { applySymbols { it.currency = Currency.getInstance(value) } }

    actual var positivePrefix: String
        get() = format.positivePrefix
        set(value) { format.positivePrefix = value }
    actual var positiveSuffix: String
        get() = format.positiveSuffix
        set(value) { format.positiveSuffix = value }
    actual var negativePrefix: String
        get() = format.negativePrefix
        set(value) { format.negativePrefix = value }
    actual var negativeSuffix: String
        get() = format.negativeSuffix
        set(value) { format.negativeSuffix = value }

    actual var groupingSeparator: Char
        get() = symbols.groupingSeparator
        set(value) { applySymbols { it.groupingSeparator = value } }
    actual var usesGroupingSeparator: Boolean
        get() = format.isGroupingUsed
        set(value) { format.isGroupingUsed = value }
    actual var decimalSeparator: Char
        get() = symbols.decimalSeparator
        set(value) { applySymbols { it.decimalSeparator = value } }
    actual var alwaysShowsDecimalSeparator: Boolean
        get() = format.isDecimalSeparatorAlwaysShown
        set(value) { format.isDecimalSeparatorAlwaysShown = value }
    actual var currencyDecimalSeparator: Char
        get() = symbols.monetaryDecimalSeparator
        set(value) { applySymbols { it.monetaryDecimalSeparator = value } }
    actual var groupingSize: Int
        get() = format.groupingSize
        set(value) { format.groupingSize = value }
    actual var multiplier: Int
        get() = format.multiplier
        set(value) { format.multiplier = value }

    actual fun format(number: Number): String = format.format(number.toDouble())
    actual fun parse(string: String): Number? = try { format.parse(string) } catch (e: ParseException) { null }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.text

import com.splendo.kaluga.base.utils.Locale

actual val lineSeparator = System.lineSeparator()

actual fun String.lowerCased(locale: Locale): String = this.lowercase(locale.locale)

actual fun String.upperCased(locale: Locale): String = this.uppercase(locale.locale)

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

import java.util.Calendar

actual class Date internal constructor(internal val calendar: Calendar) : Comparable<Date> {

    actual companion object {
        actual fun now(offsetInMilliseconds: Long, timeZone: TimeZone, locale: Locale): Date = Date(
            Calendar.getInstance(timeZone.timeZone, locale.locale).apply {
                add(Calendar.MILLISECOND, offsetInMilliseconds.toInt())
            }
        )
        actual fun epoch(offsetInMilliseconds: Long, timeZone: TimeZone, locale: Locale): Date = Date(
            Calendar.getInstance(timeZone.timeZone, locale.locale).apply {
                timeInMillis = offsetInMilliseconds
            }
        )
    }

    actual var timeZone: TimeZone
        get() = TimeZone(calendar.timeZone)
        set(value) { calendar.timeZone = value.timeZone }

    actual var era: Int
        get() = calendar.get(Calendar.ERA)
        set(value) { calendar.set(Calendar.ERA, value) }
    actual var year: Int
        get() = calendar.get(Calendar.YEAR)
        set(value) { calendar.set(Calendar.YEAR, value) }
    actual var month: Int
        get() = calendar.get(Calendar.MONTH) + 1
        set(value) { calendar.set(Calendar.MONTH, value - 1) }
    actual val daysInMonth: Int get() = calendar.getActualMaximum(Calendar.DAY_OF_MONTH)
    actual var weekOfYear: Int
        get() = calendar.get(Calendar.WEEK_OF_YEAR)
        set(value) { calendar.set(Calendar.WEEK_OF_YEAR, value) }
    actual var weekOfMonth: Int
        get() = calendar.get(Calendar.WEEK_OF_MONTH)
        set(value) { calendar.set(Calendar.WEEK_OF_MONTH, value) }
    actual var day: Int
        get() = calendar.get(Calendar.DAY_OF_MONTH)
        set(value) { calendar.set(Calendar.DAY_OF_MONTH, value) }
    actual var dayOfYear: Int
        get() = calendar.get(Calendar.DAY_OF_YEAR)
        set(value) { calendar.set(Calendar.DAY_OF_YEAR, value) }
    actual var weekDay: Int
        get() = calendar.get(Calendar.DAY_OF_WEEK)
        set(value) { calendar.set(Calendar.DAY_OF_WEEK, value) }
    actual var firstWeekDay: Int
        get() = calendar.firstDayOfWeek
        set(value) { calendar.firstDayOfWeek = value }

    actual var hour: Int
        get() = calendar.get(Calendar.HOUR_OF_DAY)
        set(value) { calendar.set(Calendar.HOUR_OF_DAY, value) }
    actual var minute: Int
        get() = calendar.get(Calendar.MINUTE)
        set(value) { calendar.set(Calendar.MINUTE, value) }
    actual var second: Int
        get() = calendar.get(Calendar.SECOND)
        set(value) { calendar.set(Calendar.SECOND, value) }
    actual var millisecond: Int
        get() = calendar.get(Calendar.MILLISECOND)
        set(value) { calendar.set(Calendar.MILLISECOND, value) }
    actual var millisecondSinceEpoch: Long
        get() = calendar.timeInMillis
        set(value) { calendar.timeInMillis = value }

    actual fun copy(): Date = Date(calendar.clone() as Calendar)

    actual override fun equals(other: Any?): Boolean {
        return (other as? Date)?.let {
            timeZone == other.timeZone && millisecondSinceEpoch == other.millisecondSinceEpoch
        } ?: false
    }

    override fun compareTo(other: Date): Int {
        return this.calendar.time.compareTo(other.calendar.time)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.

 */

@file:JvmName("DecimalJVM")
package com.splendo.kaluga.base.utils

import com.splendo.kaluga.base.utils.RoundingMode.RoundDown
import com.splendo.kaluga.base.utils.RoundingMode.RoundHalfEven
import com.splendo.kaluga.base.utils.RoundingMode.RoundUp
import java.math.BigDecimal
import java.math.MathContext
import java.math.RoundingMode as NativeRoundingMode

actual typealias Decimal = BigDecimal

actual operator fun Decimal.plus(value: Decimal) = this.add(value)

actual fun Decimal.plus(value: Decimal, scale: Int) =
    this.add(value).setScale(scale, NativeRoundingMode.HALF_EVEN)

actual fun Decimal.plus(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode
) = this.add(value).setScale(scale, roundingMode.java)

actual operator fun Decimal.minus(value: Decimal) = this.subtract(value)

actual fun Decimal.minus(value: Decimal, scale: Int) =
    this.subtract(value).setScale(scale, NativeRoundingMode.HALF_EVEN)

actual fun Decimal.minus(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode
) = this.subtract(value).setScale(scale, roundingMode.java)

actual operator fun Decimal.div(value: Decimal) = this.divide(value, MathContext.DECIMAL128)

actual fun Decimal.div(value: Decimal, scale: Int) =
    this.divide(value, MathContext.DECIMAL128).setScale(scale, NativeRoundingMode.HALF_EVEN)

actual fun Decimal.div(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode
) = this.divide(
    value,
    MathContext(
        MathContext.DECIMAL128.precision,
        NativeRoundingMode.valueOf(roundingMode.java)
    )
).setScale(scale, roundingMode.java)

actual operator fun Decimal.times(value: Decimal) =
    this.multiply(value, MathContext.DECIMAL128)

actual fun Decimal.times(value: Decimal, scale: Int) =
    this.multiply(value, MathContext.DECIMAL128).setScale(scale, NativeRoundingMode.HALF_EVEN)

actual fun Decimal.times(
    value: Decimal,
    scale: Int,
    roundingMode: RoundingMode
) = this.multiply(
    value,
    MathContext(
        MathContext.DECIMAL128.precision,
        NativeRoundingMode.valueOf(roundingMode.java)
    )
).setScale(scale, NativeRoundingMode.valueOf(roundingMode.java))

actual fun Decimal.round(scale: Int, roundingMode: RoundingMode) =
    this.setScale(
        scale,
        NativeRoundingMode.valueOf(roundingMode.java)
    )

actual infix fun Decimal.pow(n: Int): Decimal = this.pow(n, MathContext.DECIMAL128)
actual fun Decimal.pow(n: Int, scale: Int): Decimal = this.pow(n, MathContext.DECIMAL128).setScale(scale, NativeRoundingMode.HALF_EVEN)
actual fun Decimal.pow(
    n: Int,
    scale: Int,
    roundingMode: RoundingMode
): Decimal = this.pow(
    n,
    MathContext(
        MathContext.DECIMAL128.precision,
        NativeRoundingMode.valueOf(roundingMode.java)
    )
).setScale(scale, NativeRoundingMode.valueOf(roundingMode.java))

actual fun Number.toDecimal() = BigDecimal(this.toString())
actual fun String.toDecimal() = BigDecimal(this)

actual fun Decimal.toDouble() = this.toDouble()
actual fun Decimal.toInt() = this.toInt()
actual fun Decimal.toString() = this.stripTrailingZeros().toString()

val RoundingMode.java
    get() = when (this) {
        RoundDown -> NativeRoundingMode.DOWN.ordinal
        RoundHalfEven -> NativeRoundingMode.HALF_EVEN.ordinal
        RoundUp -> NativeRoundingMode.UP.ordinal
    }

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

import com.splendo.kaluga.base.text.upperCased

actual class Locale internal constructor(val locale: java.util.Locale) {
    actual companion object {
        actual fun createLocale(language: String): Locale = Locale(java.util.Locale(language))
        actual fun createLocale(language: String, country: String): Locale = Locale(java.util.Locale(language, country))
        actual fun createLocale(language: String, country: String, variant: String): Locale = Locale(java.util.Locale(language, country, variant))

        actual val defaultLocale: Locale get() = Locale(java.util.Locale.getDefault())
        actual val availableLocales: List<Locale> = java.util.Locale.getAvailableLocales().asList().map { Locale(it) }
    }

    actual val countryCode: String
        get() = locale.country
    actual val languageCode: String
        get() = locale.language
    actual val scriptCode: String
        get() = locale.script
    actual val variantCode: String
        get() = locale.variant
    actual val unitSystem: UnitSystem
        get() = UnitSystem.withCountryCode(countryCode.upperCased(this))

    actual fun name(forLocale: Locale): String = locale.getDisplayName(forLocale.locale)
    actual fun countryName(forLocale: Locale): String = locale.getDisplayCountry(forLocale.locale)
    actual fun languageName(forLocale: Locale): String = locale.getDisplayLanguage(forLocale.locale)
    actual fun variantName(forLocale: Locale): String = locale.getDisplayVariant(forLocale.locale)
    actual fun scriptName(forLocale: Locale): String = locale.getDisplayScript(forLocale.locale)

    actual val quotationStart: String = "\""
    actual val quotationEnd: String = "\""
    actual val alternateQuotationStart: String = "\""
    actual val alternateQuotationEnd: String = "\""
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils
import java.io.Serializable
actual typealias Serializable = Serializable

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.base.utils

actual class TimeZone internal constructor(internal val timeZone: java.util.TimeZone) {

    actual companion object {
        actual fun get(identifier: String): TimeZone? {
            return java.util.TimeZone.getTimeZone(identifier)?.let {
                TimeZone(it)
            }
        }
        actual fun current(): TimeZone {
            return TimeZone(java.util.TimeZone.getDefault())
        }
        actual val availableIdentifiers get() = java.util.TimeZone.getAvailableIDs().asList()
    }

    actual val identifier: String = timeZone.id
    actual fun displayName(style: TimeZoneNameStyle, withDaylightSavings: Boolean, locale: Locale): String {
        val styleJava = when (style) {
            TimeZoneNameStyle.Short -> java.util.TimeZone.SHORT
            TimeZoneNameStyle.Long -> java.util.TimeZone.LONG
        }
        return timeZone.getDisplayName(withDaylightSavings, styleJava, locale.locale)
    }
    actual val offsetFromGMTInMilliseconds = timeZone.rawOffset.toLong()
    actual val daylightSavingsOffsetInMilliseconds: Long = timeZone.dstSavings.toLong()
    actual fun offsetFromGMTAtDateInMilliseconds(date: Date): Long = timeZone.getOffset(date.millisecondSinceEpoch).toLong()
    actual fun usesDaylightSavingsTime(date: Date): Boolean = timeZone.inDaylightTime(date.calendar.time)
    actual fun copy(): TimeZone = TimeZone(timeZone.clone() as java.util.TimeZone)
    override fun equals(other: Any?): Boolean {
        return (other as? TimeZone)?.let { timeZone == other.timeZone } ?: false
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import com.splendo.kaluga.bluetooth.UUID
import no.nordicsemi.android.support.v18.scanner.ScanResult
import java.nio.ByteBuffer
import java.nio.ByteOrder

actual class AdvertisementData(private val scanResult: ScanResult?) : BaseAdvertisementData {

    private val scanRecord = scanResult?.scanRecord

    override val name: String?
        get() = scanRecord?.deviceName
    override val manufacturerId: Int?
        get() = scanRecord?.manufacturerSpecificData?.let { if (it.size() > 0) it.keyAt(0) else null }
    override val manufacturerData: ByteArray?
        get() = scanRecord?.manufacturerSpecificData?.let { manufacturerSpecificData ->
            manufacturerId?.let { key ->
                val keyBytes = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(key.toShort()).array()
                byteArrayOf(*keyBytes, *manufacturerSpecificData[key])
            }
        }
    override val serviceUUIDs: List<UUID>
        get() = scanRecord?.serviceUuids?.map { it.uuid } ?: emptyList()
    override val serviceData: Map<UUID, ByteArray?>
        get() = scanRecord?.serviceData?.mapKeys { it.key.uuid } ?: emptyMap()
    override val txPowerLevel: Int
        get() = scanRecord?.txPowerLevel ?: Int.MIN_VALUE
    override val isConnectible: Boolean
        get() = scanResult?.isConnectable == true
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import com.splendo.kaluga.bluetooth.CharacteristicWrapper
import com.splendo.kaluga.bluetooth.DescriptorWrapper

interface BluetoothGattWrapper {
    fun connect(): Boolean
    fun discoverServices(): Boolean
    fun disconnect()
    fun close()
    fun readRemoteRssi(): Boolean

    fun readCharacteristic(wrapper: CharacteristicWrapper): Boolean
    fun readDescriptor(wrapper: DescriptorWrapper): Boolean
    fun writeCharacteristic(wrapper: CharacteristicWrapper): Boolean
    fun writeDescriptor(wrapper: DescriptorWrapper): Boolean
    fun setCharacteristicNotification(wrapper: CharacteristicWrapper, enable: Boolean): Boolean
}

class DefaultBluetoothGattWrapper(private val gatt: BluetoothGatt) : BluetoothGattWrapper {

    override fun connect(): Boolean {
        return gatt.connect()
    }

    override fun discoverServices(): Boolean {
        return gatt.discoverServices()
    }

    override fun disconnect() {
        gatt.disconnect()
    }

    override fun close() {
        gatt.close()
    }

    override fun readRemoteRssi(): Boolean {
        return gatt.readRemoteRssi()
    }

    override fun readCharacteristic(wrapper: CharacteristicWrapper): Boolean {
        val characteristic = getCharacteristic(wrapper) ?: return false
        return gatt.readCharacteristic(characteristic)
    }

    override fun readDescriptor(wrapper: DescriptorWrapper): Boolean {
        val descriptor = getDescriptor(wrapper) ?: return false
        return gatt.readDescriptor(descriptor)
    }

    override fun writeCharacteristic(wrapper: CharacteristicWrapper): Boolean {
        val characteristic = getCharacteristic(wrapper) ?: return false
        return gatt.writeCharacteristic(characteristic)
    }

    override fun writeDescriptor(wrapper: DescriptorWrapper): Boolean {
        val descriptor = getDescriptor(wrapper) ?: return false
        return gatt.writeDescriptor(descriptor)
    }

    override fun setCharacteristicNotification(wrapper: CharacteristicWrapper, enable: Boolean): Boolean {
        val characteristic = getCharacteristic(wrapper) ?: return false
        return gatt.setCharacteristicNotification(characteristic, enable)
    }

    private fun getCharacteristic(wrapper: CharacteristicWrapper): BluetoothGattCharacteristic? {
        return gatt.getService(wrapper.service.uuid)?.getCharacteristic(wrapper.uuid)
    }

    private fun getDescriptor(wrapper: DescriptorWrapper): BluetoothGattDescriptor? {
        return getCharacteristic(wrapper.characteristic)?.getDescriptor(wrapper.uuid)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGatt.GATT_SUCCESS
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattCharacteristic.PROPERTY_INDICATE
import android.bluetooth.BluetoothGattCharacteristic.PROPERTY_NOTIFY
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothProfile
import android.content.Context
import com.splendo.kaluga.base.ApplicationHolder
import com.splendo.kaluga.bluetooth.Characteristic
import com.splendo.kaluga.bluetooth.DefaultGattServiceWrapper
import com.splendo.kaluga.bluetooth.Descriptor
import com.splendo.kaluga.bluetooth.Service
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.containsAnyOf
import com.splendo.kaluga.bluetooth.uuidString
import com.splendo.kaluga.logging.warn
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext

internal actual class DeviceConnectionManager(
    private val context: Context,
    connectionSettings: ConnectionSettings,
    deviceWrapper: DeviceWrapper,
    stateRepo: DeviceStateFlowRepo,
    val mainDispatcher: CoroutineContext = Dispatchers.Main.immediate // can be replaced for testing
) : BaseDeviceConnectionManager(connectionSettings, deviceWrapper, stateRepo), CoroutineScope by stateRepo {

    private companion object {
        const val TAG = "Android Bluetooth DeviceConnectionManager"
        val CLIENT_CONFIGURATION: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
    }

    override val coroutineContext: CoroutineContext
        get() = stateRepo.coroutineContext

    class Builder(private val context: Context = ApplicationHolder.applicationContext) : BaseDeviceConnectionManager.Builder {
        override fun create(
            connectionSettings: ConnectionSettings,
            deviceWrapper: DeviceWrapper,
            stateRepo: DeviceStateFlowRepo,
        ): BaseDeviceConnectionManager {
            return DeviceConnectionManager(context, connectionSettings, deviceWrapper, stateRepo)
        }
    }

    private val device: BluetoothDevice = deviceWrapper.device
    private var gatt: CompletableDeferred<BluetoothGattWrapper> = CompletableDeferred()
    private val callback = object : BluetoothGattCallback() {

        override fun onReadRemoteRssi(gatt: BluetoothGatt?, rssi: Int, status: Int) {
            launch(mainDispatcher) {
                handleNewRssi(rssi)
            }
        }

        override fun onCharacteristicRead(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
            characteristic ?: return
            updateCharacteristic(characteristic, status)
        }

        override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
            characteristic ?: return
            updateCharacteristic(characteristic, status)
        }

        override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
            launch(mainDispatcher) {
                val services = gatt?.services?.map { Service(DefaultGattServiceWrapper(it), stateRepo) } ?: emptyList()
                handleScanCompleted(services)
            }
        }

        override fun onDescriptorWrite(gatt: BluetoothGatt?, descriptor: BluetoothGattDescriptor?, status: Int) {
            descriptor ?: return
            updateDescriptor(descriptor, status)
        }

        override fun onCharacteristicChanged(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?) {
            characteristic ?: return
            updateCharacteristic(characteristic, status = GATT_SUCCESS)
        }

        override fun onDescriptorRead(gatt: BluetoothGatt?, descriptor: BluetoothGattDescriptor?, status: Int) {
            descriptor ?: return
            updateDescriptor(descriptor, status)
        }

        override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
            lastKnownState = newState
            launch(mainDispatcher) {
                when (newState) {
                    BluetoothProfile.STATE_DISCONNECTED -> {
                        handleDisconnect {
                            closeGatt()
                        }
                    }
                    BluetoothProfile.STATE_CONNECTED -> {
                        handleConnect()
                    }
                }
            }
        }
    }
    private var lastKnownState = BluetoothProfile.STATE_DISCONNECTED

    override suspend fun connect() {
        if (lastKnownState != BluetoothProfile.STATE_CONNECTED || !gatt.isCompleted) {
            unpair()
            if (gatt.isCompleted) {
                if (!gatt.getCompleted().connect()) {
                    launch(mainDispatcher) {
                        handleDisconnect { closeGatt() }
                    }
                } else if (lastKnownState != BluetoothProfile.STATE_CONNECTED) {
                    launch(mainDispatcher) {
                        handleConnect()
                    }
                }
            } else {
                val gattService = device.connectGatt(context, false, callback)
                gatt.complete(DefaultBluetoothGattWrapper(gattService))
            }
        } else {
            launch(mainDispatcher) {
                handleConnect()
            }
        }
    }

    override suspend fun discoverServices() {
        gatt.await().discoverServices()
    }

    override suspend fun disconnect() {
        if (lastKnownState != BluetoothProfile.STATE_DISCONNECTED)
            gatt.await().disconnect()
        else
            launch(mainDispatcher) {
                handleDisconnect {
                    closeGatt()
                }
            }
    }

    private fun closeGatt() {
        if (gatt.isCompleted) {
            gatt.getCompleted().close()
        }
        gatt = CompletableDeferred()
    }

    override suspend fun readRssi() {
        gatt.await().readRemoteRssi()
    }

    override suspend fun performAction(action: DeviceAction) {
        currentAction = action
        val succeeded = when (action) {
            is DeviceAction.Read.Characteristic -> gatt.await().readCharacteristic(action.characteristic.wrapper)
            is DeviceAction.Read.Descriptor -> gatt.await().readDescriptor(action.descriptor.wrapper)
            is DeviceAction.Write.Characteristic -> writeCharacteristic(action.characteristic, action.newValue)
            is DeviceAction.Write.Descriptor -> writeDescriptor(action.descriptor, action.newValue)
            is DeviceAction.Notification.Enable -> setNotification(action.characteristic, true)
            is DeviceAction.Notification.Disable -> setNotification(action.characteristic, false)
        }

        // Action Failed
        if (!succeeded) {
            launch(mainDispatcher) {
                handleCurrentActionCompleted(succeeded = false)
            }
        }
    }

    private suspend fun writeCharacteristic(characteristic: Characteristic, value: ByteArray?): Boolean {
        characteristic.wrapper.updateValue(value)
        return gatt.await().writeCharacteristic(characteristic.wrapper)
    }

    private suspend fun writeDescriptor(descriptor: Descriptor, value: ByteArray?): Boolean {
        descriptor.wrapper.updateValue(value)
        return gatt.await().writeDescriptor(descriptor.wrapper)
    }

    private suspend fun setNotification(characteristic: Characteristic, enable: Boolean): Boolean {
        val uuid = characteristic.uuid.uuidString
        if (enable) notifyingCharacteristics[uuid] = characteristic
        else notifyingCharacteristics.remove(uuid)
        if (!gatt.await().setCharacteristicNotification(characteristic.wrapper, enable)) {
            return false
        }

        val writeValue = when {
            enable && characteristic.wrapper.containsAnyOf(PROPERTY_NOTIFY) ->
                BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
            enable && characteristic.wrapper.containsAnyOf(PROPERTY_INDICATE) ->
                BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
            !enable && characteristic.wrapper.containsAnyOf(PROPERTY_INDICATE, PROPERTY_NOTIFY) ->
                BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
            else -> null
        }

        return if (writeValue != null) {
            characteristic.descriptors.firstOrNull { it.uuid == CLIENT_CONFIGURATION }?.let { descriptor ->
                descriptor.wrapper.updateValue(writeValue)
                gatt.await().writeDescriptor(descriptor.wrapper)
            } ?: false
        } else {
            warn(TAG) {
                "(${characteristic.uuid.uuidString}) Failed attempt to perform set notification action. " +
                    "neither NOTIFICATION nor INDICATION is supported. " +
                    "Supported properties: ${characteristic.wrapper.properties}"
            }
            false
        }
    }

    private fun updateCharacteristic(characteristic: BluetoothGattCharacteristic, status: Int) {
        launch(mainDispatcher) {
            handleUpdatedCharacteristic(characteristic.uuid, succeeded = status == GATT_SUCCESS) {
                it.wrapper.updateValue(characteristic.value)
            }
        }
    }

    private fun updateDescriptor(descriptor: BluetoothGattDescriptor, status: Int) {
        launch(mainDispatcher) {
            val succeeded = status == GATT_SUCCESS
            // Notification enable/disable done by client configuration descriptor write
            if (descriptor.uuid == CLIENT_CONFIGURATION && currentAction is DeviceAction.Notification) {
                handleCurrentActionCompleted(succeeded)
            }
            handleUpdatedDescriptor(descriptor.uuid, succeeded) {
                it.wrapper.updateValue(descriptor.value)
            }
        }
    }

    private fun unpair() {
        // unpair to prevent connection problems
        if (device.bondState != BluetoothDevice.BOND_NONE) {
            deviceWrapper.removeBond()
        }
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

actual typealias Identifier = String

actual val Identifier.stringValue: String
    get() = this

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGattCallback
import android.content.Context
import android.os.Build

actual interface DeviceWrapper {
    actual val name: String?
    actual val identifier: Identifier
    val bondState: Int
    fun connectGatt(context: Context, autoConnect: Boolean, callback: BluetoothGattCallback): BluetoothGattWrapper
    fun removeBond()

    val device: BluetoothDevice
}

class DefaultDeviceWrapper(override val device: BluetoothDevice) : DeviceWrapper {

    override val name: String?
        get() = device.name
    override val identifier: Identifier
        get() = device.address
    override val bondState: Int
        get() = device.bondState

    override fun connectGatt(context: Context, autoConnect: Boolean, callback: BluetoothGattCallback): BluetoothGattWrapper {
        val gatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            device.connectGatt(context, autoConnect, callback, BluetoothDevice.TRANSPORT_LE)
        } else {
            device.connectGatt(context, autoConnect, callback)
        }
        return DefaultBluetoothGattWrapper(gatt)
    }

    override fun removeBond() {
        try {
            device.javaClass.getMethod("removeBond").invoke(device)
        } catch (localException: Exception) {
        }
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.scanner

import android.bluetooth.BluetoothAdapter
import android.content.Context
import android.os.ParcelUuid
import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.base.ApplicationHolder
import com.splendo.kaluga.base.flow.filterOnlyImportant
import com.splendo.kaluga.bluetooth.BluetoothMonitor
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.device.AdvertisementData
import com.splendo.kaluga.bluetooth.device.ConnectionSettings
import com.splendo.kaluga.bluetooth.device.DefaultDeviceWrapper
import com.splendo.kaluga.bluetooth.device.Device
import com.splendo.kaluga.bluetooth.device.DeviceConnectionManager
import com.splendo.kaluga.bluetooth.device.DeviceInfoImpl
import com.splendo.kaluga.location.EnableLocationActivity
import com.splendo.kaluga.location.LocationMonitor
import com.splendo.kaluga.permissions.PermissionState
import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.permissions.location.LocationPermission
import com.splendo.kaluga.state.StateRepo
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import no.nordicsemi.android.support.v18.scanner.BluetoothLeScannerCompat
import no.nordicsemi.android.support.v18.scanner.ScanCallback
import no.nordicsemi.android.support.v18.scanner.ScanFilter
import no.nordicsemi.android.support.v18.scanner.ScanResult
import no.nordicsemi.android.support.v18.scanner.ScanSettings

actual class Scanner internal constructor(
    private val bluetoothScanner: BluetoothLeScannerCompat = BluetoothLeScannerCompat.getScanner(),
    private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter(),
    private val scanSettings: ScanSettings = defaultScanSettings,
    private val applicationContext: Context = ApplicationHolder.applicationContext,
    permissions: Permissions,
    connectionSettings: ConnectionSettings,
    autoRequestPermission: Boolean,
    autoEnableSensors: Boolean,
    stateRepo: StateRepo<ScanningState, MutableStateFlow<ScanningState>>,
) : BaseScanner(permissions, connectionSettings, autoRequestPermission, autoEnableSensors, stateRepo) {

    class Builder(
        private val bluetoothScanner: BluetoothLeScannerCompat = BluetoothLeScannerCompat.getScanner(),
        private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter(),
        private val scanSettings: ScanSettings = defaultScanSettings,
        private val applicationContext: Context = ApplicationHolder.applicationContext,
    ) : BaseScanner.Builder {

        override fun create(
            permissions: Permissions,
            connectionSettings: ConnectionSettings,
            autoRequestPermission: Boolean,
            autoEnableSensors: Boolean,
            scanningStateRepo: StateRepo<ScanningState, MutableStateFlow<ScanningState>>,
        ): BaseScanner {
            return Scanner(bluetoothScanner, bluetoothAdapter, scanSettings, applicationContext, permissions, connectionSettings, autoRequestPermission, autoEnableSensors, scanningStateRepo)
        }
    }

    companion object {
        val defaultScanSettings = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_BALANCED)
            .setNumOfMatches(ScanSettings.MATCH_NUM_FEW_ADVERTISEMENT)
            .build()

        private val locationPermission = LocationPermission(background = false, precise = true)
    }

    private val callback = object : ScanCallback() {

        override fun onScanFailed(errorCode: Int) {
            val error = when (errorCode) {
                SCAN_FAILED_ALREADY_STARTED -> Pair("Already Started", false)
                SCAN_FAILED_SCANNING_TOO_FREQUENTLY -> Pair("Scanning Too Frequently", true)
                SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES -> Pair("Out of Resources", true)
                SCAN_FAILED_INTERNAL_ERROR -> Pair("Internal Error", true)
                SCAN_FAILED_FEATURE_UNSUPPORTED -> Pair("Feature Unsupported", true)
                SCAN_FAILED_APPLICATION_REGISTRATION_FAILED -> Pair("Registration Failed", true)
                else -> Pair("Reason Unknown", true)
            }

            launch(stateRepo.coroutineContext) {
                stateRepo.peekState().logError(Error(error.first))
                if (error.second) {
                    stateRepo.takeAndChangeState { state ->
                        when (state) {
                            is ScanningState.Initialized.Enabled.Scanning -> state.stopScanning
                            else -> state.remain()
                        }
                    }
                }
            }
        }

        override fun onScanResult(callbackType: Int, result: ScanResult) {
            if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) return
            handleScanResult(result)
        }

        override fun onBatchScanResults(results: MutableList<ScanResult>) {
            results.forEach {
                handleScanResult(it)
            }
        }

        private fun handleScanResult(scanResult: ScanResult) {
            val advertisementData = AdvertisementData(scanResult)
            val deviceWrapper = DefaultDeviceWrapper(scanResult.device)

            handleDeviceDiscovered(deviceWrapper.identifier, scanResult.rssi, advertisementData) {
                val deviceInfo = DeviceInfoImpl(deviceWrapper, scanResult.rssi, advertisementData)
                Device(connectionSettings, deviceInfo, deviceConnectionManagerBuilder, stateRepo.coroutineContext)
            }
        }
    }

    private val locationPermissionRepo get() = permissions[locationPermission]

    override val isSupported: Boolean = bluetoothAdapter != null
    private val deviceConnectionManagerBuilder = DeviceConnectionManager.Builder(applicationContext)
    override val bluetoothEnabledMonitor: BluetoothMonitor? = bluetoothAdapter?.let { BluetoothMonitor.Builder(applicationContext, it).create() }
    private val locationEnabledMonitor = LocationMonitor.Builder(applicationContext).create()

    private val monitoringLocationPermissionsJob = AtomicReference<Job?>(null)
    private val monitoringLocationEnabledJob = AtomicReference<Job?>(null)

    override suspend fun scanForDevices(filter: Set<UUID>) {
        bluetoothScanner.startScan(
            filter.map {
                ScanFilter.Builder().setServiceUuid(ParcelUuid(it)).build()
            },
            scanSettings,
            callback
        )
    }

    override suspend fun stopScanning() {
        bluetoothScanner.stopScan(callback)
    }

    override fun startMonitoringPermissions() {
        super.startMonitoringPermissions()
        if (monitoringLocationPermissionsJob.get() != null) return // optimization to skip making a job

        val job = Job(this.coroutineContext[Job])

        if (monitoringLocationPermissionsJob.compareAndSet(null, job)) {
            launch(job) {
                locationPermissionRepo.collect { state ->
                    handlePermissionState(state, locationPermission)
                }
            }
        }
    }

    override fun stopMonitoringPermissions() {
        super.stopMonitoringPermissions()
        monitoringLocationPermissionsJob.get()?.let {
            if (monitoringLocationPermissionsJob.compareAndSet(it, null)) {
                it.cancel()
            }
        }
    }

    override fun startMonitoringSensors() {
        super.startMonitoringSensors()
        if (monitoringLocationEnabledJob.get() != null) return // optimization to skip making a job

        val job = Job(this.coroutineContext[Job])

        if (monitoringLocationEnabledJob.compareAndSet(null, job)) {
            locationEnabledMonitor.startMonitoring()
            launch(job) {
                locationEnabledMonitor.isEnabled.collect {
                    checkSensorsEnabledChanged()
                }
            }
        }
    }

    override fun stopMonitoringSensors() {
        super.stopMonitoringSensors()
        monitoringLocationEnabledJob.get()?.let {
            locationEnabledMonitor.stopMonitoring()
            if (monitoringLocationEnabledJob.compareAndSet(it, null))
                it.cancel()
        }
    }

    override suspend fun isPermitted(): Boolean {
        return super.isPermitted() && locationPermissionRepo.filterOnlyImportant().first() is PermissionState.Allowed
    }

    override suspend fun areSensorsEnabled(): Boolean = super.areSensorsEnabled() && locationEnabledMonitor.isServiceEnabled

    override fun generateEnableSensorsActions(): List<EnableSensorAction> {
        if (!isSupported) return emptyList()
        return listOfNotNull(
            if (bluetoothAdapter?.isEnabled != true) suspend {
                bluetoothAdapter?.enable()
                bluetoothEnabledMonitor!!.isEnabled.first { it }
            } else null,
            if (!locationEnabledMonitor.isServiceEnabled) {
                EnableLocationActivity.showEnableLocationActivity(applicationContext, hashCode().toString())::await
            } else null
        )
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import com.splendo.kaluga.bluetooth.UUID

interface BaseAdvertisementData {
    val name: String?
    val manufacturerId: Int?
    val manufacturerData: ByteArray?
    val serviceUUIDs: List<UUID>
    val serviceData: Map<UUID, ByteArray?>
    val txPowerLevel: Int
    val isConnectible: Boolean
}

expect class AdvertisementData : BaseAdvertisementData

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

data class ConnectionSettings(val reconnectionSettings: ReconnectionSettings = ReconnectionSettings.Always) {

    sealed class ReconnectionSettings {
        object Always : ReconnectionSettings()
        object Never : ReconnectionSettings()
        data class Limited(val attempts: Int) : ReconnectionSettings()
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import co.touchlab.stately.collections.sharedMutableMapOf
import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.bluetooth.Characteristic
import com.splendo.kaluga.bluetooth.Descriptor
import com.splendo.kaluga.bluetooth.Service
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.uuidString
import com.splendo.kaluga.logging.debug
import kotlinx.coroutines.CoroutineScope

abstract class BaseDeviceConnectionManager(
    val connectionSettings: ConnectionSettings = ConnectionSettings(),
    val deviceWrapper: DeviceWrapper,
    val stateRepo: DeviceStateFlowRepo
) : CoroutineScope by stateRepo {

    private companion object {
        const val TAG = "DeviceConnectionManager"
    }

    interface Builder {
        fun create(
            connectionSettings: ConnectionSettings,
            deviceWrapper: DeviceWrapper,
            stateRepo: DeviceStateFlowRepo
        ): BaseDeviceConnectionManager
    }

    private val _currentAction = AtomicReference<DeviceAction?>(null)
    protected var currentAction: DeviceAction?
        get() = _currentAction.get()
        set(value) { _currentAction.set(value) }
    protected val notifyingCharacteristics = sharedMutableMapOf<String, Characteristic>()

    abstract suspend fun connect()
    abstract suspend fun discoverServices()
    abstract suspend fun disconnect()
    abstract suspend fun readRssi()
    abstract suspend fun performAction(action: DeviceAction)

    suspend fun handleNewRssi(rssi: Int) {
        stateRepo.takeAndChangeState {
            it.rssiDidUpdate(rssi)
        }
    }

    suspend fun handleConnect() {
        stateRepo.takeAndChangeState { state ->
            when (state) {
                is DeviceState.Connecting -> state.didConnect
                is DeviceState.Reconnecting -> state.didConnect
                is DeviceState.Connected -> state.remain()
                else -> {
                    currentAction = null
                    notifyingCharacteristics.clear()
                    disconnect()
                    state.remain()
                }
            }
        }
    }

    suspend fun handleDisconnect(onDisconnect: (suspend () -> Unit)? = null) {
        val clean = suspend {
            currentAction = null
            notifyingCharacteristics.clear()
            onDisconnect?.invoke()
        }

        stateRepo.takeAndChangeState { state ->
            when (state) {
                is DeviceState.Reconnecting -> {
                    state.retry().also {
                        if (it == state.didDisconnect) {
                            clean()
                        }
                    }
                }
                is DeviceState.Connected -> when (connectionSettings.reconnectionSettings) {
                    is ConnectionSettings.ReconnectionSettings.Always,
                    is ConnectionSettings.ReconnectionSettings.Limited -> state.reconnect
                    is ConnectionSettings.ReconnectionSettings.Never -> {
                        clean()
                        state.didDisconnect
                    }
                }
                is DeviceState.Disconnected -> state.remain()
                is DeviceState.Connecting,
                is DeviceState.Disconnecting -> {
                    clean()
                    state.didDisconnect
                }
            }
        }
    }

    suspend fun handleScanCompleted(services: List<Service>) {
        stateRepo.takeAndChangeState { state ->
            when (state) {
                is DeviceState.Connected.Discovering -> state.didDiscoverServices(services)
                else -> state.remain()
            }
        }
    }

    open suspend fun handleCurrentActionCompleted(succeeded: Boolean) = stateRepo.takeAndChangeState { state ->
        (
            if (state is DeviceState.Connected.HandlingAction && state.action == currentAction) {
                state.action.completedSuccessfully.complete(succeeded)
                debug(TAG) { "Action $currentAction has been succeeded: $succeeded" }
                state.actionCompleted
            } else {
                state.remain()
            }
            ).also { currentAction = null }
    }

    suspend fun handleUpdatedCharacteristic(uuid: UUID, succeeded: Boolean, onUpdate: ((Characteristic) -> Unit)? = null) {
        notifyingCharacteristics[uuid.uuidString]?.updateValue()
        val characteristicToUpdate = when (val action = currentAction) {
            is DeviceAction.Read.Characteristic -> {
                if (action.characteristic.uuid.uuidString == uuid.uuidString) {
                    action.characteristic
                } else null
            }
            is DeviceAction.Write.Characteristic -> {
                if (action.characteristic.uuid.uuidString == uuid.uuidString) {
                    action.characteristic
                } else null
            }
            else -> null
        }

        characteristicToUpdate?.let {
            onUpdate?.invoke(it)
            it.updateValue()
            handleCurrentActionCompleted(succeeded)
        }
    }

    suspend fun handleUpdatedDescriptor(uuid: UUID, succeeded: Boolean, onUpdate: ((Descriptor) -> Unit)? = null) {
        val descriptorToUpdate = when (val action = currentAction) {
            is DeviceAction.Read.Descriptor -> {
                if (action.descriptor.uuid.uuidString == uuid.uuidString) {
                    action.descriptor
                } else null
            }
            is DeviceAction.Write.Descriptor -> {
                if (action.descriptor.uuid.uuidString == uuid.uuidString) {
                    action.descriptor
                } else null
            }
            else -> null
        }

        descriptorToUpdate?.let {
            onUpdate?.invoke(it)
            it.updateValue()
            handleCurrentActionCompleted(succeeded)
        }
    }
}

internal expect class DeviceConnectionManager : BaseDeviceConnectionManager

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import com.splendo.kaluga.base.utils.Date
import kotlin.math.pow

expect class Identifier

expect val Identifier.stringValue: String

expect interface DeviceWrapper {
    val name: String?
    val identifier: Identifier
}

interface DeviceInfo {
    val identifier: Identifier
    val name: String?
    val rssi: Int
    val advertisementData: BaseAdvertisementData
    val updatedAt: Date
    fun distance(environmentalFactor: Double = 2.0): Double
}

data class DeviceInfoImpl(
    internal val deviceWrapper: DeviceWrapper,
    override val rssi: Int,
    override val advertisementData: BaseAdvertisementData
) : DeviceInfo {

    override val identifier: Identifier
        get() = deviceWrapper.identifier

    override val name: String?
        get() = deviceWrapper.name

    override val updatedAt = Date.now()

    override fun distance(environmentalFactor: Double): Double {
        if (advertisementData.txPowerLevel == Int.MIN_VALUE || environmentalFactor.isNaN())
            return Double.NaN
        val difference = advertisementData.txPowerLevel.toDouble() - rssi.toDouble()
        val factor = 10.0 * environmentalFactor
        return 10.0.pow(difference / factor)
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import com.splendo.kaluga.bluetooth.Service
import com.splendo.kaluga.state.HandleAfterOldStateIsRemoved
import com.splendo.kaluga.state.HotStateFlowRepo
import com.splendo.kaluga.state.State
import com.splendo.kaluga.state.StateRepo
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext

sealed class DeviceAction {

    /**
     * A Deferred that will be completed with
     * `true` if `DeviceAction` was succeeded, or
     * `false` if `DeviceAction` was failed
     * */
    val completedSuccessfully = CompletableDeferred<Boolean>()

    sealed class Read : DeviceAction() {
        class Characteristic(val characteristic: com.splendo.kaluga.bluetooth.Characteristic) : Read()
        class Descriptor(val descriptor: com.splendo.kaluga.bluetooth.Descriptor) : Read()
    }

    sealed class Write(val newValue: ByteArray?) : DeviceAction() {
        class Characteristic(newValue: ByteArray?, val characteristic: com.splendo.kaluga.bluetooth.Characteristic) : Write(newValue)
        class Descriptor(newValue: ByteArray?, val descriptor: com.splendo.kaluga.bluetooth.Descriptor) : Write(newValue)
    }

    sealed class Notification(val characteristic: com.splendo.kaluga.bluetooth.Characteristic) : DeviceAction() {
        class Enable(characteristic: com.splendo.kaluga.bluetooth.Characteristic) : Notification(characteristic)
        class Disable(characteristic: com.splendo.kaluga.bluetooth.Characteristic) : Notification(characteristic)
    }
}

typealias DeviceStateFlowRepo = StateRepo<DeviceState, MutableStateFlow<DeviceState>>

sealed class DeviceState(
    open val deviceInfo: DeviceInfoImpl,
    open val connectionManager: BaseDeviceConnectionManager
) : State(), DeviceInfo by deviceInfo, CoroutineScope by connectionManager {

    sealed class Connected(
        deviceInfo: DeviceInfoImpl,
        connectionManager: BaseDeviceConnectionManager
    ) : DeviceState(deviceInfo, connectionManager) {

        data class NoServices constructor(
            override val deviceInfo: DeviceInfoImpl,
            override val connectionManager: BaseDeviceConnectionManager
        ) : Connected(deviceInfo, connectionManager) {

            fun startDiscovering() {
                launch(coroutineContext) {
                    connectionManager.stateRepo.takeAndChangeState { deviceState ->
                        if (deviceState is NoServices)
                            discoverServices
                        else
                            deviceState.remain()
                    }
                }
            }

            val discoverServices = suspend {
                Discovering(deviceInfo, connectionManager)
            }
        }

        data class Discovering constructor(
            override val deviceInfo: DeviceInfoImpl,
            override val connectionManager: BaseDeviceConnectionManager
        ) : Connected(deviceInfo, connectionManager),
            HandleAfterOldStateIsRemoved<DeviceState> {

            internal fun didDiscoverServices(services: List<Service>): suspend () -> Idle {
                return { Idle(services, deviceInfo, connectionManager) }
            }

            override suspend fun afterOldStateIsRemoved(oldState: DeviceState) {
                connectionManager.discoverServices()
            }
        }

        data class Idle constructor(
            val services: List<Service>,
            override val deviceInfo: DeviceInfoImpl,
            override val connectionManager: BaseDeviceConnectionManager
        ) : Connected(deviceInfo, connectionManager) {

            fun handleAction(action: DeviceAction): suspend () -> HandlingAction {
                return { HandlingAction(action, emptyList(), services, deviceInfo, connectionManager) }
            }
        }

        data class HandlingAction constructor(
            internal val action: DeviceAction,
            internal val nextActions: List<DeviceAction>,
            val services: List<Service>,
            override val deviceInfo: DeviceInfoImpl,
            override val connectionManager: BaseDeviceConnectionManager
        ) : Connected(deviceInfo, connectionManager), HandleAfterOldStateIsRemoved<DeviceState> {

            fun addAction(newAction: DeviceAction): suspend () -> HandlingAction {
                return { HandlingAction(action, listOf(*nextActions.toTypedArray(), newAction), services, deviceInfo, connectionManager) }
            }

            internal val actionCompleted = suspend {
                when (action) {
                    is DeviceAction.Read.Characteristic -> action.characteristic.updateValue()
                    is DeviceAction.Read.Descriptor -> action.descriptor.updateValue()
                    is DeviceAction.Write.Characteristic -> action.characteristic.updateValue()
                    is DeviceAction.Write.Descriptor -> action.descriptor.updateValue()
                    is DeviceAction.Notification.Enable -> action.characteristic.updateValue()
                    is DeviceAction.Notification.Disable -> { }
                }
                if (nextActions.isEmpty()) {
                    Idle(services, deviceInfo, connectionManager)
                } else {
                    val nextAction = nextActions.first()
                    val remainingActions = nextActions.drop(1)
                    HandlingAction(nextAction, remainingActions, services, deviceInfo, connectionManager)
                }
            }

            override suspend fun afterOldStateIsRemoved(oldState: DeviceState) {
                if (oldState is HandlingAction && oldState.action == action)
                    return
                connectionManager.performAction(action)
            }
        }

        fun startDisconnected() =
            connectionManager.stateRepo.launchTakeAndChangeState(remainIfStateNot = Connected::class) {
                disconnecting
            }

        val reconnect = suspend {
            // All services, characteristics and descriptors become invalidated after it disconnects
            Reconnecting(0, null, deviceInfo, connectionManager)
        }

        suspend fun readRssi() {
            connectionManager.readRssi()
        }

        override suspend fun finalState() {
            super.finalState()

            connectionManager.disconnect()
        }
    }
    data class Connecting constructor(
        override val deviceInfo: DeviceInfoImpl,
        override val connectionManager: BaseDeviceConnectionManager
    ) : DeviceState(deviceInfo, connectionManager), HandleAfterOldStateIsRemoved<DeviceState> {

        val cancelConnection = disconnecting

        fun handleCancel() {
            launch(coroutineContext) {
                connectionManager.stateRepo.takeAndChangeState { deviceState ->
                    if (deviceState is Connecting) {
                        cancelConnection
                    } else {
                        deviceState.remain()
                    }
                }
            }
        }

        internal val didConnect = suspend {
            Connected.NoServices(deviceInfo, connectionManager)
        }

        override suspend fun afterOldStateIsRemoved(oldState: DeviceState) {
            when (oldState) {
                is Disconnected -> connectionManager.connect()
                is Connected,
                is Connecting,
                is Reconnecting,
                is Disconnecting -> {
                    // do nothing: TODO check all these are correct, e.g. Disconnecting
                }
            }
        }
    }

    data class Reconnecting constructor(
        val attempt: Int,
        val services: List<Service>?,
        override val deviceInfo: DeviceInfoImpl,
        override val connectionManager: BaseDeviceConnectionManager
    ) : DeviceState(deviceInfo, connectionManager), HandleAfterOldStateIsRemoved<DeviceState> {

        fun retry(): suspend () -> DeviceState {
            return when (val reconnectionSetting = connectionManager.connectionSettings.reconnectionSettings) {
                is ConnectionSettings.ReconnectionSettings.Always -> remain()
                is ConnectionSettings.ReconnectionSettings.Never -> didDisconnect
                is ConnectionSettings.ReconnectionSettings.Limited -> {
                    val nextAttempt = attempt + 1
                    if (nextAttempt < reconnectionSetting.attempts) {
                        suspend { copy(attempt = nextAttempt) }
                    } else {
                        didDisconnect
                    }
                }
            }
        }

        fun handleCancel() {
            launch(coroutineContext) {
                connectionManager.stateRepo.takeAndChangeState { deviceState ->
                    if (deviceState is Reconnecting)
                        cancelConnection
                    else
                        deviceState.remain()
                }
            }
        }

        private val cancelConnection = disconnecting

        internal val didConnect = suspend {
            services?.let { Connected.Idle(services, deviceInfo, connectionManager) } ?: Connected.NoServices(deviceInfo, connectionManager)
        }

        override suspend fun afterOldStateIsRemoved(oldState: DeviceState) {
            when (oldState) {
                is Connected, is Reconnecting -> connectionManager.connect()
                else -> {}
            }
        }
    }

    data class Disconnected constructor(
        override val deviceInfo: DeviceInfoImpl,
        override val connectionManager: BaseDeviceConnectionManager
    ) : DeviceState(deviceInfo, connectionManager) {

        fun startConnecting() = connectionManager.stateRepo.launchTakeAndChangeState(
            coroutineContext,
            remainIfStateNot = Disconnected::class
        ) { deviceState ->
            connect(deviceState)
        }

        fun connect(deviceState: DeviceState): suspend () -> DeviceState =
            if (deviceInfo.advertisementData.isConnectible)
                suspend {
                    Connecting(deviceInfo, connectionManager)
                }
            else
                deviceState.remain()
    }

    data class Disconnecting constructor(
        override val deviceInfo: DeviceInfoImpl,
        override val connectionManager: BaseDeviceConnectionManager
    ) : DeviceState(deviceInfo, connectionManager), HandleAfterOldStateIsRemoved<DeviceState> {

        override suspend fun afterOldStateIsRemoved(oldState: DeviceState) {
            when (oldState) {
                is Connecting, is Reconnecting, is Connected -> connectionManager.disconnect()
                else -> {}
            }
        }
    }

    fun rssiDidUpdate(rssi: Int): suspend () -> DeviceState {
        return updateDeviceInfo(deviceInfo.copy(rssi = rssi))
    }

    fun advertisementDataAndRssiDidUpdate(advertisementData: BaseAdvertisementData, rssi: Int): suspend () -> DeviceState {
        return updateDeviceInfo(deviceInfo.copy(rssi = rssi, advertisementData = advertisementData))
    }

    private fun updateDeviceInfo(newDeviceInfo: DeviceInfoImpl): suspend () -> DeviceState {
        return {
            when (this) {
                is Connected.NoServices -> copy(deviceInfo = newDeviceInfo)
                is Connected.Idle -> copy(deviceInfo = newDeviceInfo)
                is Connected.HandlingAction -> copy(deviceInfo = newDeviceInfo)
                is Connected.Discovering -> copy(deviceInfo = newDeviceInfo)
                is Connecting -> copy(deviceInfo = newDeviceInfo)
                is Reconnecting -> copy(deviceInfo = newDeviceInfo)
                is Disconnecting -> copy(deviceInfo = newDeviceInfo)
                is Disconnected -> copy(deviceInfo = newDeviceInfo)
            }
        }
    }

    internal val didDisconnect = suspend {
        Disconnected(deviceInfo, connectionManager)
    }

    internal val disconnecting = suspend {
        Disconnecting(deviceInfo, connectionManager)
    }
}

class Device constructor(
    connectionSettings: ConnectionSettings,
    private val initialDeviceInfo: DeviceInfoImpl,
    connectionBuilder: BaseDeviceConnectionManager.Builder,
    coroutineContext: CoroutineContext
) : HotStateFlowRepo<DeviceState>(
    coroutineContext = coroutineContext,
    initialState = {
        val deviceConnectionManager = connectionBuilder.create(connectionSettings, initialDeviceInfo.deviceWrapper, it)
        DeviceState.Disconnected(initialDeviceInfo, deviceConnectionManager)
    }
) {
    val identifier: Identifier
        get() = initialDeviceInfo.identifier
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.scanner

import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.base.flow.filterOnlyImportant
import com.splendo.kaluga.bluetooth.BluetoothMonitor
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.device.AdvertisementData
import com.splendo.kaluga.bluetooth.device.ConnectionSettings
import com.splendo.kaluga.bluetooth.device.Device
import com.splendo.kaluga.bluetooth.device.Identifier
import com.splendo.kaluga.bluetooth.scanner.ScanningState.Initialized
import com.splendo.kaluga.bluetooth.scanner.ScanningState.Initialized.Enabled
import com.splendo.kaluga.bluetooth.scanner.ScanningState.Initialized.NoBluetooth.Disabled
import com.splendo.kaluga.permissions.Permission
import com.splendo.kaluga.permissions.PermissionState
import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.permissions.bluetooth.BluetoothPermission
import com.splendo.kaluga.state.StateRepo
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.fold
import kotlinx.coroutines.launch

typealias EnableSensorAction = suspend () -> Boolean

abstract class BaseScanner constructor(
    internal val permissions: Permissions,
    private val connectionSettings: ConnectionSettings,
    protected val autoRequestPermission: Boolean,
    internal val autoEnableSensors: Boolean,
    internal val stateRepo: StateRepo<ScanningState, MutableStateFlow<ScanningState>>,
) : CoroutineScope by stateRepo {

    interface Builder {
        fun create(
            permissions: Permissions,
            connectionSettings: ConnectionSettings,
            autoRequestPermission: Boolean,
            autoEnableSensors: Boolean,
            scanningStateRepo: ScanningStateFlowRepo
        ): BaseScanner
    }

    abstract val isSupported: Boolean
    private val bluetoothPermissionRepo get() = permissions[BluetoothPermission]
    protected abstract val bluetoothEnabledMonitor: BluetoothMonitor?

    private val _monitoringPermissionsJob = AtomicReference<Job?>(null)
    private var monitoringPermissionsJob: Job?
        get() = _monitoringPermissionsJob.get()
        set(value) { _monitoringPermissionsJob.set(value) }

    private val _monitoringBluetoothEnabledJob = AtomicReference<Job?>(null)
    private var monitoringBluetoothEnabledJob: Job?
        get() = _monitoringBluetoothEnabledJob.get()
        set(value) { _monitoringBluetoothEnabledJob.set(value) }

    open fun startMonitoringPermissions() {
        if (monitoringPermissionsJob != null) return
        monitoringPermissionsJob = launch(stateRepo.coroutineContext) {
            bluetoothPermissionRepo.collect { state ->
                handlePermissionState(state, BluetoothPermission)
            }
        }
    }

    protected suspend fun <P : Permission> handlePermissionState(state: PermissionState<P>, permission: P) {
        when (state) {
            is PermissionState.Denied.Requestable -> if (autoRequestPermission) state.request(permissions.getManager(permission))
            else -> {}
        }
        stateRepo.takeAndChangeState { scanState ->
            when (scanState) {
                is Disabled, is Enabled -> {
                    if (isPermitted())
                        scanState.remain()
                    else
                        (scanState as? Initialized)?.revokePermission ?: scanState.remain()
                }
                is Initialized.NoBluetooth.MissingPermissions -> if (isPermitted()) scanState.permit(areSensorsEnabled()) else scanState.remain()
                else -> { scanState.remain() }
            }
        }
    }

    open fun stopMonitoringPermissions() {
        monitoringPermissionsJob?.cancel()
        monitoringPermissionsJob = null
    }

    internal open suspend fun isPermitted(): Boolean {
        return bluetoothPermissionRepo.filterOnlyImportant().first() is PermissionState.Allowed
    }

    abstract suspend fun scanForDevices(filter: Set<UUID>)
    abstract suspend fun stopScanning()
    open fun startMonitoringSensors() {
        val bluetoothEnabledMonitor = bluetoothEnabledMonitor ?: return
        bluetoothEnabledMonitor.startMonitoring()
        if (monitoringBluetoothEnabledJob != null) return
        monitoringBluetoothEnabledJob = launch {
            bluetoothEnabledMonitor.isEnabled.collect {
                checkSensorsEnabledChanged()
            }
        }
    }
    open fun stopMonitoringSensors() {
        val bluetoothEnabledMonitor = bluetoothEnabledMonitor ?: return
        bluetoothEnabledMonitor.stopMonitoring()
        monitoringBluetoothEnabledJob?.cancel()
        monitoringBluetoothEnabledJob = null
    }
    open suspend fun areSensorsEnabled(): Boolean = bluetoothEnabledMonitor?.isServiceEnabled ?: false
    suspend fun requestSensorsEnable() {
        val actions = generateEnableSensorsActions()
        if (actions.isEmpty()) {
            checkSensorsEnabledChanged()
        } else if (
            flowOf(*actions.toTypedArray()).fold(true) { acc, action ->
                acc && action()
            }
        ) {
            requestSensorsEnable()
        }
    }
    abstract fun generateEnableSensorsActions(): List<EnableSensorAction>

    fun bluetoothEnabled() = stateRepo.launchTakeAndChangeState(remainIfStateNot = Disabled::class) {
        it.enable
    }

    fun bluetoothDisabled() = stateRepo.launchTakeAndChangeState(remainIfStateNot = Enabled::class) {
        it.disable
    }

    internal fun handleDeviceDiscovered(identifier: Identifier, rssi: Int, advertisementData: AdvertisementData, deviceCreator: () -> Device) =
        stateRepo.launchTakeAndChangeState { state ->
            when (state) {
                is Enabled.Scanning -> {
                    state.discoverDevice(identifier, rssi, advertisementData, deviceCreator)
                }
                else -> {
                    state.logError(Error("Discovered Device while not scanning"))
                    state.remain()
                }
            }
        }

    internal open suspend fun checkSensorsEnabledChanged() {
        when {
            areSensorsEnabled() -> bluetoothEnabled()
            else -> bluetoothDisabled()
        }
    }
}

expect class Scanner : BaseScanner

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.scanner

import com.splendo.kaluga.base.flow.SpecialFlowValue
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.device.BaseAdvertisementData
import com.splendo.kaluga.bluetooth.device.ConnectionSettings
import com.splendo.kaluga.bluetooth.device.Device
import com.splendo.kaluga.bluetooth.device.Identifier
import com.splendo.kaluga.bluetooth.scanner.ScanningState.Initialized.Enabled.Scanning
import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.state.ColdStateFlowRepo
import com.splendo.kaluga.state.HandleAfterCreating
import com.splendo.kaluga.state.HandleAfterOldStateIsRemoved
import com.splendo.kaluga.state.HandleBeforeOldStateIsRemoved
import com.splendo.kaluga.state.State
import com.splendo.kaluga.state.StateRepo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlin.coroutines.CoroutineContext

typealias Filter = Set<UUID>

sealed class ScanningState : State() {

    data class Discovered(
        val devices: List<Device>,
        internal val filter: Filter,
    ) {
        constructor(filter: Filter) : this(emptyList(), filter)

        fun copyAndAdd(device: Device): Discovered =
            Discovered(listOf(*devices.toTypedArray(), device), filter)

        fun discoveredForFilter(filter: Filter) =
            if (this.filter == filter)
                this
            else
                Discovered(filter)
    }

    companion object {
        val nothingDiscovered = Discovered(emptySet())
        const val TAG = "BluetoothManager"
    }

    fun logError(error: Error) {
        error.message?.let { com.splendo.kaluga.logging.error(TAG, it) }
    }

    sealed class Initialized(private val scanner: BaseScanner) : ScanningState(), HandleBeforeOldStateIsRemoved<ScanningState> {

        override suspend fun beforeOldStateIsRemoved(oldState: ScanningState) {
            if (oldState is NotInitialized) {
                // Start monitoring Permissions and Bluetooth
                // Stop will be called from deinitChangeState
                scanner.startMonitoringPermissions()
                scanner.startMonitoringSensors()
            }
        }

        val revokePermission = suspend { NoBluetooth.MissingPermissions(scanner) }

        sealed class Enabled(
            val discovered: Discovered,
            protected val scanner: BaseScanner
        ) : Initialized(scanner) {

            val disable = suspend {
                NoBluetooth.Disabled(scanner)
            }

            class Idle internal constructor(
                previouslyDiscovered: Discovered,
                scanner: BaseScanner
            ) : Enabled(previouslyDiscovered, scanner) {

                fun startScanning(filter: Set<UUID> = discovered.filter): suspend () -> Scanning = {
                    Scanning(
                        discovered.discoveredForFilter(filter),
                        scanner
                    )
                }

                fun refresh(filter: Set<UUID> = discovered.filter): suspend () -> Idle = {
                    Idle(
                        discovered.discoveredForFilter(filter),
                        scanner
                    )
                }
            }

            class Scanning internal constructor(
                discovered: Discovered,
                scanner: BaseScanner
            ) : Enabled(discovered, scanner),
                HandleAfterOldStateIsRemoved<ScanningState>,
                HandleAfterCreating<ScanningState> {

                suspend fun discoverDevice(
                    identifier: Identifier,
                    rssi: Int,
                    advertisementData: BaseAdvertisementData,
                    deviceCreator: () -> Device
                ): suspend () -> ScanningState {

                    return discovered.devices.find { it.identifier == identifier }
                        ?.let { knownDevice ->
                            knownDevice.takeAndChangeState { state ->
                                state.advertisementDataAndRssiDidUpdate(advertisementData, rssi)
                            }
                            // TODO our contents have technically changed yet we remain()
                            // not storing Devices as repos, but rather storing their _state_
                            // would be better.
                            remain()
                        } ?: suspend { Scanning(discovered.copyAndAdd(deviceCreator()), scanner) }
                }

                val stopScanning = suspend { Idle(discovered, scanner) }

                override suspend fun afterOldStateIsRemoved(oldState: ScanningState) {
                    if (oldState !is Scanning)
                        scanner.scanForDevices(discovered.filter)
                }

                override suspend fun afterCreatingNewState(newState: ScanningState) {
                    if (newState !is Scanning)
                        scanner.stopScanning()
                }
            }
        }

        sealed class NoBluetooth constructor(scanner: BaseScanner) : Initialized(scanner) {

            class Disabled internal constructor(
                private val scanner: BaseScanner
            ) : NoBluetooth(scanner), HandleAfterOldStateIsRemoved<ScanningState> {

                val enable: suspend () -> Enabled = {
                    Enabled.Idle(nothingDiscovered, scanner)
                }

                override suspend fun afterOldStateIsRemoved(oldState: ScanningState) {
                    if (oldState !is Disabled && scanner.autoEnableSensors)
                        scanner.requestSensorsEnable()
                }
            }

            class MissingPermissions internal constructor(
                private val scanner: BaseScanner
            ) : NoBluetooth(scanner) {

                fun permit(enabled: Boolean): suspend () -> ScanningState = {
                    if (enabled) Enabled.Idle(nothingDiscovered, scanner)
                    else Disabled(scanner)
                }
            }

            class NoHardware internal constructor(
                private val scanner: BaseScanner
            ) : NoBluetooth(scanner)
        }

        internal fun stopMonitoring() {
            scanner.stopMonitoringPermissions()
            scanner.stopMonitoringSensors()
        }
    }

    class NotInitialized(
        val permissions: Permissions,
        val connectionSettings: ConnectionSettings,
        val autoRequestPermission: Boolean,
        val autoEnableBluetooth: Boolean,
        val builder: BaseScanner.Builder
    ) : ScanningState(), SpecialFlowValue.NotImportant {

        suspend fun initialize(
            repo: StateRepo<ScanningState, MutableStateFlow<ScanningState>>
        ): suspend () -> Initialized {
            val scanner = builder.create(
                permissions,
                connectionSettings,
                autoRequestPermission,
                autoEnableBluetooth,
                scanningStateRepo = repo
            )
            return if (!scanner.isSupported) {
                { Initialized.NoBluetooth.NoHardware(scanner) }
            } else if (!scanner.isPermitted()) {
                { Initialized.NoBluetooth.MissingPermissions(scanner) }
            } else if (!scanner.areSensorsEnabled()) {
                { Initialized.NoBluetooth.Disabled(scanner) }
            } else {
                { Initialized.Enabled.Idle(nothingDiscovered, scanner) }
            }
        }
    }
}

typealias ScanningStateFlowRepo = StateRepo<ScanningState, MutableStateFlow<ScanningState>>

class ScanningStateRepo(
    permissions: Permissions,
    connectionSettings: ConnectionSettings,
    autoRequestPermission: Boolean,
    autoEnableBluetooth: Boolean,
    builder: BaseScanner.Builder,
    coroutineContext: CoroutineContext = Dispatchers.Main.immediate
) : ColdStateFlowRepo<ScanningState>(
    coroutineContext = coroutineContext,
    initChangeState = { state ->
        when (state) {
            is ScanningState.Initialized.Enabled.Idle ->
                state.refresh() // check if we now need to start scanning again
            is Scanning,
            is ScanningState.Initialized.NoBluetooth,
            is ScanningState.NotInitialized ->
                state.remain()
        }
    },
    deinitChangeState = { state ->
        if (state is ScanningState.Initialized)
            state.stopMonitoring()
        if (state is Scanning)
            state.stopScanning
        else
            state.remain()
    },
    firstState = {
        ScanningState.NotInitialized(
            permissions,
            connectionSettings,
            autoRequestPermission,
            autoEnableBluetooth,
            builder
        )
    }
)

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import com.splendo.kaluga.base.toByteArray
import com.splendo.kaluga.base.typedList
import com.splendo.kaluga.base.typedMap
import com.splendo.kaluga.bluetooth.UUID
import platform.CoreBluetooth.CBAdvertisementDataIsConnectable
import platform.CoreBluetooth.CBAdvertisementDataLocalNameKey
import platform.CoreBluetooth.CBAdvertisementDataManufacturerDataKey
import platform.CoreBluetooth.CBAdvertisementDataServiceDataKey
import platform.CoreBluetooth.CBAdvertisementDataServiceUUIDsKey
import platform.CoreBluetooth.CBAdvertisementDataTxPowerLevelKey
import platform.CoreBluetooth.CBUUID
import platform.Foundation.NSData
import platform.Foundation.NSNumber

actual class AdvertisementData(private val advertisementData: Map<String, Any>) : BaseAdvertisementData {

    override val name: String?
        get() = advertisementData[CBAdvertisementDataLocalNameKey] as? String
    override val manufacturerId: Int?
        get() = manufacturerData?.let { manufacturerDataArray ->
            if (manufacturerDataArray.size >= 2)
                (manufacturerDataArray[0].toUInt() + (manufacturerDataArray[1].toUInt() shl 8)).toInt()
            else null
        }
    override val manufacturerData: ByteArray? get() = (advertisementData[CBAdvertisementDataManufacturerDataKey] as? NSData)?.toByteArray()
    override val serviceUUIDs: List<UUID> get() = (advertisementData[CBAdvertisementDataServiceUUIDsKey] as? List<*>)?.typedList() ?: emptyList()
    override val serviceData: Map<UUID, ByteArray?> get() = (advertisementData[CBAdvertisementDataServiceDataKey] as? Map<*, *>)
        ?.typedMap<CBUUID, NSData>()
        ?.mapNotNull { Pair(it.key, it.value.toByteArray()) }
        ?.toMap() ?: emptyMap()
    override val txPowerLevel: Int get() = (advertisementData[CBAdvertisementDataTxPowerLevelKey] as? NSNumber)?.intValue ?: Int.MIN_VALUE

    override val isConnectible: Boolean get() = ((advertisementData[CBAdvertisementDataIsConnectable] as? NSNumber)?.boolValue ?: false)
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import co.touchlab.stately.collections.sharedMutableListOf
import com.splendo.kaluga.base.toNSData
import com.splendo.kaluga.base.typedList
import com.splendo.kaluga.bluetooth.DefaultServiceWrapper
import com.splendo.kaluga.bluetooth.Service
import com.splendo.kaluga.bluetooth.uuidString
import kotlinx.coroutines.launch
import platform.CoreBluetooth.CBCentralManager
import platform.CoreBluetooth.CBCharacteristic
import platform.CoreBluetooth.CBDescriptor
import platform.CoreBluetooth.CBPeripheral
import platform.CoreBluetooth.CBPeripheralDelegateProtocol
import platform.CoreBluetooth.CBService
import platform.CoreBluetooth.CBUUID
import platform.Foundation.NSError
import platform.Foundation.NSNumber
import platform.darwin.NSObject

internal actual class DeviceConnectionManager(
    private val cbCentralManager: CBCentralManager,
    private val peripheral: CBPeripheral,
    connectionSettings: ConnectionSettings,
    deviceWrapper: DeviceWrapper,
    stateRepo: DeviceStateFlowRepo,
) : BaseDeviceConnectionManager(connectionSettings, deviceWrapper, stateRepo) {

    class Builder(private val cbCentralManager: CBCentralManager, private val peripheral: CBPeripheral) : BaseDeviceConnectionManager.Builder {
        override fun create(
            connectionSettings: ConnectionSettings,
            deviceWrapper: DeviceWrapper,
            stateRepo: DeviceStateFlowRepo
        ): BaseDeviceConnectionManager {
            return DeviceConnectionManager(
                cbCentralManager,
                peripheral,
                connectionSettings,
                deviceWrapper,
                stateRepo
            )
        }
    }

    companion object {
        private const val TAG = "IOS Bluetooth DeviceConnectionManager"
    }

    private val discoveringServices = sharedMutableListOf<CBUUID>()
    private val discoveringCharacteristics = sharedMutableListOf<CBUUID>()

    @Suppress("CONFLICTING_OVERLOADS")
    private val peripheralDelegate = object : NSObject(), CBPeripheralDelegateProtocol {

        override fun peripheral(peripheral: CBPeripheral, didDiscoverDescriptorsForCharacteristic: CBCharacteristic, error: NSError?) {
            didDiscoverDescriptors(didDiscoverDescriptorsForCharacteristic)
        }

        override fun peripheral(peripheral: CBPeripheral, didUpdateNotificationStateForCharacteristic: CBCharacteristic, error: NSError?) {
            when (val action = currentAction) {
                is DeviceAction.Notification -> {
                    if (action.characteristic.wrapper.uuid == didUpdateNotificationStateForCharacteristic.UUID) {
                        launch {
                            handleCurrentActionCompleted(succeeded = error == null)
                        }
                    }
                }
            }
        }

        override fun peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic: CBCharacteristic, error: NSError?) {
            updateCharacteristic(didUpdateValueForCharacteristic, error)
        }

        override fun peripheral(peripheral: CBPeripheral, didWriteValueForCharacteristic: CBCharacteristic, error: NSError?) {
            updateCharacteristic(didWriteValueForCharacteristic, error)
        }

        override fun peripheral(peripheral: CBPeripheral, didUpdateValueForDescriptor: CBDescriptor, error: NSError?) {
            updateDescriptor(didUpdateValueForDescriptor, error)
        }

        override fun peripheral(peripheral: CBPeripheral, didWriteValueForDescriptor: CBDescriptor, error: NSError?) {
            updateDescriptor(didWriteValueForDescriptor, error)
        }

        override fun peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService: CBService, error: NSError?) {
            didDiscoverCharacteristic(didDiscoverCharacteristicsForService)
        }

        override fun peripheral(peripheral: CBPeripheral, didDiscoverServices: NSError?) {
            didDiscoverServices()
        }

        override fun peripheral(peripheral: CBPeripheral, didReadRSSI: NSNumber, error: NSError?) {
            launch {
                handleNewRssi(didReadRSSI.intValue)
            }
        }
    }

    init {
        peripheral.delegate = peripheralDelegate
    }

    override suspend fun connect() {
        cbCentralManager.connectPeripheral(peripheral, null)
    }

    override suspend fun discoverServices() {
        discoveringServices.clear()
        discoveringCharacteristics.clear()
        peripheral.discoverServices(null)
    }

    override suspend fun disconnect() {
        cbCentralManager.cancelPeripheralConnection(peripheral)
    }

    override suspend fun readRssi() {
        peripheral.readRSSI()
    }

    override suspend fun performAction(action: DeviceAction) {
        currentAction = action
        when (action) {
            is DeviceAction.Read.Characteristic -> action.characteristic.wrapper.readValue(peripheral)
            is DeviceAction.Read.Descriptor -> action.descriptor.wrapper.readValue(peripheral)
            is DeviceAction.Write.Characteristic -> {
                action.newValue?.toNSData()?.let {
                    action.characteristic.wrapper.writeValue(it, peripheral)
                }
            }
            is DeviceAction.Write.Descriptor -> {
                action.newValue?.toNSData()?.let {
                    action.descriptor.wrapper.writeValue(it, peripheral)
                }
            }
            is DeviceAction.Notification.Enable -> {
                val uuid = action.characteristic.uuid.uuidString
                notifyingCharacteristics[uuid] = action.characteristic
                action.characteristic.wrapper.setNotificationValue(true, peripheral)
            }
            is DeviceAction.Notification.Disable -> {
                val uuid = action.characteristic.uuid.uuidString
                notifyingCharacteristics.remove(uuid)
                action.characteristic.wrapper.setNotificationValue(false, peripheral)
            }
        }
    }

    private fun updateCharacteristic(characteristic: CBCharacteristic, error: NSError?) {
        launch {
            handleUpdatedCharacteristic(characteristic.UUID, succeeded = error == null)
        }
    }

    private fun updateDescriptor(descriptor: CBDescriptor, error: NSError?) {
        launch {
            handleUpdatedDescriptor(descriptor.UUID, succeeded = error == null)
        }
    }

    private fun didDiscoverServices() {
        discoveringServices.addAll(
            peripheral.services?.typedList<CBService>()?.map {
                peripheral.discoverCharacteristics(emptyList<CBUUID>(), it)
                it.UUID
            } ?: emptyList()
        )

        checkScanComplete()
    }

    private fun didDiscoverCharacteristic(forService: CBService) {
        discoveringServices.remove(forService.UUID)
        discoveringCharacteristics.addAll(
            forService.characteristics?.typedList<CBCharacteristic>()?.map {
                peripheral.discoverDescriptorsForCharacteristic(it)
                it.UUID
            } ?: emptyList()
        )
        checkScanComplete()
    }

    private fun didDiscoverDescriptors(forCharacteristic: CBCharacteristic) {
        discoveringCharacteristics.remove(forCharacteristic.UUID)
        checkScanComplete()
    }

    private fun checkScanComplete() {
        if (discoveringServices.isEmpty() && discoveringCharacteristics.isEmpty()) {
            launch {
                val services = peripheral.services?.typedList<CBService>()?.map { Service(DefaultServiceWrapper(it), stateRepo) } ?: emptyList()
                handleScanCompleted(services)
            }
        }
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import platform.Foundation.NSUUID

actual typealias Identifier = NSUUID

actual val Identifier.stringValue: String
    get() = UUIDString

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import platform.CoreBluetooth.CBPeripheral
import platform.Foundation.NSUUID

actual interface DeviceWrapper {

    actual val name: String?
    actual val identifier: NSUUID
}

class DefaultCBPeripheralWrapper(private val peripheral: CBPeripheral) : DeviceWrapper {

    override val name: String? get() { return peripheral.name }
    override val identifier: NSUUID get() { return peripheral.identifier }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.scanner

import co.touchlab.stately.collections.sharedMutableListOf
import co.touchlab.stately.collections.sharedMutableSetOf
import com.splendo.kaluga.base.mainContinuation
import com.splendo.kaluga.base.typedMap
import com.splendo.kaluga.base.utils.EmptyCompletableDeferred
import com.splendo.kaluga.base.utils.complete
import com.splendo.kaluga.bluetooth.BluetoothMonitor
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.device.AdvertisementData
import com.splendo.kaluga.bluetooth.device.BaseDeviceConnectionManager
import com.splendo.kaluga.bluetooth.device.ConnectionSettings
import com.splendo.kaluga.bluetooth.device.DefaultCBPeripheralWrapper
import com.splendo.kaluga.bluetooth.device.Device
import com.splendo.kaluga.bluetooth.device.DeviceConnectionManager
import com.splendo.kaluga.bluetooth.device.DeviceInfoImpl
import com.splendo.kaluga.permissions.Permissions
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.flow.first
import platform.CoreBluetooth.CBCentralManager
import platform.CoreBluetooth.CBCentralManagerDelegateProtocol
import platform.CoreBluetooth.CBCentralManagerOptionShowPowerAlertKey
import platform.CoreBluetooth.CBCentralManagerStatePoweredOn
import platform.CoreBluetooth.CBPeripheral
import platform.Foundation.NSError
import platform.Foundation.NSNumber
import platform.darwin.NSObject
import platform.darwin.dispatch_get_main_queue

actual class Scanner internal constructor(
    permissions: Permissions,
    private val connectionSettings: ConnectionSettings,
    autoRequestPermission: Boolean,
    autoEnableSensors: Boolean,
    stateRepo: ScanningStateFlowRepo,
) : BaseScanner(permissions, connectionSettings, autoRequestPermission, autoEnableSensors, stateRepo) {

    class Builder : BaseScanner.Builder {

        override fun create(
            permissions: Permissions,
            connectionSettings: ConnectionSettings,
            autoRequestPermission: Boolean,
            autoEnableSensors: Boolean,
            scanningStateRepo: ScanningStateFlowRepo,
        ): BaseScanner {
            return Scanner(permissions, connectionSettings, autoRequestPermission, autoEnableSensors, scanningStateRepo)
        }
    }

    companion object {
        private const val TAG = "IOS Bluetooth Scanner"
    }

    private class EnabledCBCentralManagerDelegate(private val isCheckEnabledCompleted: CompletableDeferred<Boolean>) : NSObject(), CBCentralManagerDelegateProtocol {
        override fun centralManagerDidUpdateState(central: CBCentralManager) = mainContinuation {
            val isEnabled = central.state == CBCentralManagerStatePoweredOn
            isCheckEnabledCompleted.complete(isEnabled)
        }.invoke()
    }

    @Suppress("CONFLICTING_OVERLOADS")
    private class PoweredOnCBCentralManagerDelegate(private val scanner: Scanner, private val isEnabledCompleted: EmptyCompletableDeferred) : NSObject(), CBCentralManagerDelegateProtocol {

        override fun centralManagerDidUpdateState(central: CBCentralManager) = mainContinuation {
            if (central.state == CBCentralManagerStatePoweredOn) {
                isEnabledCompleted.complete()
            }
        }.invoke()

        override fun centralManager(central: CBCentralManager, didDiscoverPeripheral: CBPeripheral, advertisementData: Map<Any?, *>, RSSI: NSNumber) {
            scanner.discoverPeripheral(central, didDiscoverPeripheral, advertisementData.typedMap(), RSSI.intValue)
        }

        fun handlePeripheral(didConnectPeripheral: CBPeripheral, block: suspend BaseDeviceConnectionManager.() -> Unit) = mainContinuation {
            scanner.stateRepo.launchUseState { scannerState ->
                if (scannerState is ScanningState.Initialized.Enabled)
                    scannerState.discovered.devices.find { it.identifier == didConnectPeripheral.identifier }
                        ?.let { device ->
                            block(device.peekState().connectionManager)
                        }
            }
        }.invoke()

        override fun centralManager(central: CBCentralManager, didConnectPeripheral: CBPeripheral) {
            handlePeripheral(didConnectPeripheral) { handleConnect() }
        }

        override fun centralManager(central: CBCentralManager, didDisconnectPeripheral: CBPeripheral, error: NSError?) {
            handlePeripheral(didDisconnectPeripheral) { handleDisconnect() }
        }

        override fun centralManager(central: CBCentralManager, didFailToConnectPeripheral: CBPeripheral, error: NSError?) {
            handlePeripheral(didFailToConnectPeripheral) { handleDisconnect() }
        }
    }

    override val isSupported: Boolean = true
    private lateinit var mainCentralManager: CBCentralManager
    private lateinit var checkEnabledCentralManager: CBCentralManager
    private val centralManagers = sharedMutableListOf<CBCentralManager>()
    private val discoveringDelegates = sharedMutableListOf<CBCentralManagerDelegateProtocol>()
    private val activeDelegates = sharedMutableSetOf<CBCentralManagerDelegateProtocol>()
    override val bluetoothEnabledMonitor: BluetoothMonitor by lazy {
        initMainManagersIfNeeded()
        BluetoothMonitor.Builder(mainCentralManager).create()
    }

    private fun initMainManagersIfNeeded() {
        if (!::mainCentralManager.isInitialized) {
            mainCentralManager = CBCentralManager(null, dispatch_get_main_queue(), emptyMap<Any?, Any>())
        }
        if (!::checkEnabledCentralManager.isInitialized) {
            checkEnabledCentralManager = CBCentralManager(null, dispatch_get_main_queue(), emptyMap<Any?, Any>())
        }
    }

    private suspend fun scan(filter: UUID? = null) {
        val centralManager = CBCentralManager(null, dispatch_get_main_queue())
        centralManagers.add(centralManager)
        val awaitPoweredOn = EmptyCompletableDeferred()
        val delegate = PoweredOnCBCentralManagerDelegate(this, awaitPoweredOn)
        discoveringDelegates.add(delegate)
        centralManager.delegate = delegate
        awaitPoweredOn.await()
        centralManager.scanForPeripheralsWithServices(filter?.let { listOf(filter) }, null)
    }

    override suspend fun scanForDevices(filter: Set<UUID>) =
        if (filter.isEmpty())
            scan()
        else
            filter.forEach { scan(it) }

    override suspend fun stopScanning() {
        centralManagers.forEach { centralManager ->
            if (centralManager.state == CBCentralManagerStatePoweredOn) {
                centralManager.stopScan()
            }
        }
        discoveringDelegates.clear()
        centralManagers.clear()
    }

    override fun generateEnableSensorsActions(): List<EnableSensorAction> {
        // Trigger Enable Bluetooth popup
        return listOfNotNull(
            if (!bluetoothEnabledMonitor.isServiceEnabled) {
                suspend {
                    val options =
                        mapOf<Any?, Any>(CBCentralManagerOptionShowPowerAlertKey to true)
                    CBCentralManager(null, dispatch_get_main_queue(), options)
                    bluetoothEnabledMonitor.isEnabled.first { it }
                }
            } else null
        )
    }

    private fun discoverPeripheral(central: CBCentralManager, peripheral: CBPeripheral, advertisementDataMap: Map<String, Any>, rssi: Int) {
        initMainManagersIfNeeded()

        central.delegate?.let {
            activeDelegates.add(it)
        }

        val advertisementData = AdvertisementData(advertisementDataMap)
        val deviceWrapper = DefaultCBPeripheralWrapper(peripheral)
        handleDeviceDiscovered(deviceWrapper.identifier, rssi, advertisementData) {
            val deviceInfo = DeviceInfoImpl(deviceWrapper, rssi, advertisementData)
            Device(
                connectionSettings,
                deviceInfo,
                DeviceConnectionManager.Builder(central, peripheral),
                coroutineContext
            )
        }
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import com.splendo.kaluga.bluetooth.UUID

actual class AdvertisementData : BaseAdvertisementData {

    override val name: String?
        get() = null
    override val manufacturerId: Int?
        get() = null
    override val manufacturerData: ByteArray?
        get() = null
    override val serviceUUIDs: List<UUID>
        get() = emptyList()
    override val serviceData: Map<UUID, ByteArray?>
        get() = emptyMap()
    override val txPowerLevel: Int
        get() = Int.MIN_VALUE
    override val isConnectible: Boolean
        get() = false
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

internal actual class DeviceConnectionManager(connectionSettings: ConnectionSettings, deviceWrapper: DeviceWrapper, stateRepo: DeviceStateFlowRepo) : BaseDeviceConnectionManager(connectionSettings, deviceWrapper, stateRepo) {

    class Builder() : BaseDeviceConnectionManager.Builder {

        override fun create(
            connectionSettings: ConnectionSettings,
            deviceWrapper: DeviceWrapper,
            stateRepo: DeviceStateFlowRepo,
        ): DeviceConnectionManager {
            return DeviceConnectionManager(connectionSettings, deviceWrapper, stateRepo)
        }
    }

    override suspend fun connect() {}

    override suspend fun discoverServices() {}

    override suspend fun disconnect() {}

    override suspend fun readRssi() {}

    override suspend fun performAction(action: DeviceAction) {}
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import com.splendo.kaluga.bluetooth.UUID

actual typealias Identifier = UUID

actual val Identifier.stringValue: String
    get() = uuidString

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

actual interface DeviceWrapper {
    actual val name: String?
    actual val identifier: Identifier
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.scanner

import com.splendo.kaluga.bluetooth.BluetoothMonitor
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.device.ConnectionSettings
import com.splendo.kaluga.permissions.Permissions

actual class Scanner(
    permissions: Permissions,
    connectionSettings: ConnectionSettings,
    autoRequestPermission: Boolean,
    autoEnableSensors: Boolean,
    stateRepo: ScanningStateFlowRepo
) : BaseScanner(permissions, connectionSettings, autoRequestPermission, autoEnableSensors, stateRepo) {

    class Builder : BaseScanner.Builder {

        override fun create(
            permissions: Permissions,
            connectionSettings: ConnectionSettings,
            autoRequestPermission: Boolean,
            autoEnableSensors: Boolean,
            scanningStateRepo: ScanningStateFlowRepo,
        ): BaseScanner {
            return Scanner(permissions, connectionSettings, autoRequestPermission, autoEnableSensors, scanningStateRepo)
        }
    }

    override val isSupported: Boolean = false
    override val bluetoothEnabledMonitor: BluetoothMonitor = BluetoothMonitor.Builder().create()

    override suspend fun scanForDevices(filter: Set<UUID>) {}

    override suspend fun stopScanning() {}

    override fun startMonitoringSensors() {}

    override fun stopMonitoringSensors() {}

    override fun generateEnableSensorsActions(): List<EnableSensorAction> = emptyList()
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import com.splendo.kaluga.bluetooth.UUID

actual class AdvertisementData : BaseAdvertisementData {

    override val name: String?
        get() = null
    override val manufacturerId: Int?
        get() = null
    override val manufacturerData: ByteArray?
        get() = null
    override val serviceUUIDs: List<UUID>
        get() = emptyList()
    override val serviceData: Map<UUID, ByteArray?>
        get() = emptyMap()
    override val txPowerLevel: Int
        get() = Int.MIN_VALUE
    override val isConnectible: Boolean
        get() = false
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

internal actual class DeviceConnectionManager(connectionSettings: ConnectionSettings, deviceWrapper: DeviceWrapper, stateRepo: DeviceStateFlowRepo) : BaseDeviceConnectionManager(connectionSettings, deviceWrapper, stateRepo) {

    class Builder : BaseDeviceConnectionManager.Builder {

        override fun create(connectionSettings: ConnectionSettings, deviceWrapper: DeviceWrapper, stateRepo: DeviceStateFlowRepo): DeviceConnectionManager {
            return DeviceConnectionManager(connectionSettings, deviceWrapper, stateRepo)
        }
    }

    override suspend fun connect() {}

    override suspend fun discoverServices() {}

    override suspend fun disconnect() {}

    override suspend fun readRssi() {}

    override suspend fun performAction(action: DeviceAction) {}
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

import com.splendo.kaluga.bluetooth.UUID

actual typealias Identifier = UUID

actual val Identifier.stringValue: String
    get() = uuidString

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.device

actual interface DeviceWrapper {
    actual val name: String?
    actual val identifier: Identifier
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.bluetooth.scanner

import com.splendo.kaluga.bluetooth.BluetoothMonitor
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.device.ConnectionSettings
import com.splendo.kaluga.permissions.Permissions

actual class Scanner(
    permissions: Permissions,
    connectionSettings: ConnectionSettings,
    autoRequestPermission: Boolean,
    autoEnableSensors: Boolean,
    stateRepo: ScanningStateFlowRepo
) : BaseScanner(permissions, connectionSettings, autoRequestPermission, autoEnableSensors, stateRepo) {

    class Builder : BaseScanner.Builder {

        override fun create(
            permissions: Permissions,
            connectionSettings: ConnectionSettings,
            autoRequestPermission: Boolean,
            autoEnableSensors: Boolean,
            scanningStateRepo: ScanningStateFlowRepo,
        ): BaseScanner {
            return Scanner(permissions, connectionSettings, autoRequestPermission, autoEnableSensors, scanningStateRepo)
        }
    }

    override val isSupported: Boolean = false
    override val bluetoothEnabledMonitor: BluetoothMonitor = BluetoothMonitor.Builder().create()

    override suspend fun scanForDevices(filter: Set<UUID>) {}

    override suspend fun stopScanning() {}

    override fun startMonitoringSensors() {}

    override fun stopMonitoringSensors() {}

    override fun generateEnableSensorsActions(): List<EnableSensorAction> = emptyList()
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.av

import com.splendo.kaluga.permissions.camera.CameraPermission
import com.splendo.kaluga.permissions.camera.CameraPermissionManager
import platform.AVFoundation.AVMediaTypeVideo

const val NSCameraUsageDescription = "NSCameraUsageDescription"
const val NSMicrophoneUsageDescription = "NSMicrophoneUsageDescription"

class AVTypeCamera(override val permissionManager: CameraPermissionManager) : AVType<CameraPermission>() {

    override val avMediaType = AVMediaTypeVideo
    override val declarationName = NSCameraUsageDescription
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.example.shared

import com.splendo.kaluga.alerts.Alert
import com.splendo.kaluga.alerts.BaseAlertPresenter
import com.splendo.kaluga.alerts.buildActionSheet
import com.splendo.kaluga.alerts.buildAlert
import com.splendo.kaluga.alerts.buildAlertWithInput
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.logging.debug
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class AlertViewModel(val builder: BaseAlertPresenter.Builder) : BaseViewModel() {

    fun showAlert() {
        coroutineScope.launch {
            val okAction = Alert.Action("OK", Alert.Action.Style.POSITIVE)
            val cancelAction = Alert.Action("Cancel", Alert.Action.Style.NEGATIVE)
            val alert = builder.buildAlert(this) {
                setTitle("Hello, Kaluga 🐟")
                setMessage("This is sample message")
                addActions(okAction, cancelAction)
            }
            when (alert.show()) {
                okAction -> debug("OK pressed")
                cancelAction -> debug("Cancel pressed")
            }
        }
    }

    fun showAndDismissAfter(timeSecs: Long) {
        coroutineScope.launch {
            val job = launch {
                builder.buildAlert(coroutineScope) {
                    setTitle("Wait for $timeSecs sec...")
                    setPositiveButton("OK")
                }.show()
            }
            launch {
                delay(timeSecs * 1_000)
                job.cancel()
            }
        }
    }

    fun showAlertWithInput() {
        coroutineScope.launch {
            val okAction = Alert.Action("OK", Alert.Action.Style.POSITIVE)
            val cancelAction = Alert.Action("Cancel", Alert.Action.Style.NEGATIVE)
            val alert = builder.buildAlertWithInput(this) {
                setTitle("Hello, Kaluga 🐟")
                setMessage("Type something!")
                addActions(okAction, cancelAction)
                setTextInput(placeholder = "This is a sample hint..") {
                    debug("Input value changed to: $it")
                }
            }
            when (alert.show()) {
                okAction -> debug("OK pressed")
                cancelAction -> debug("Cancel pressed")
            }
        }
    }

    fun showAlertWithList() {
        coroutineScope.launch {
            builder.buildActionSheet(this) {
                setTitle("Select an option")
                addActions(
                    Alert.Action("Option 1") { debug("Option 1") },
                    Alert.Action("Option 2") { debug("Option 2") },
                    Alert.Action("Option 3") { debug("Option 3") },
                    Alert.Action("Option 4") { debug("Option 4") }
                )
            }.show()
        }
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.example.shared

fun helloCommon(): String {
    return "Hello from the shared module common source"
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.example.shared

import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.hud.BaseHUD
import com.splendo.kaluga.hud.HUDStyle
import com.splendo.kaluga.hud.build
import com.splendo.kaluga.hud.dismissAfter
import com.splendo.kaluga.hud.presentDuring
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class HudViewModel(val builder: BaseHUD.Builder) : BaseViewModel() {

    fun onShowSystemPressed() {
        // SYSTEM style by default
        // No title by default
        coroutineScope.launch {
            builder.build(this).present().dismissAfter(3_000)
        }
    }

    fun onShowCustomPressed() {
        coroutineScope.launch {
            builder.build(this) {
                setStyle(HUDStyle.CUSTOM)
                setTitle("This is a custom title")
            }.presentDuring {
                // Simulate heavy task
                delay(3_000)
            }
        }
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.location

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.CompletableDeferred

class EnableLocationActivity : AppCompatActivity() {

    companion object {
        private const val EXTRA_CALLBACK_ID = "EXTRA_CALLBACK_ID"
        val enablingHandlers: MutableMap<String, CompletableDeferred<Boolean>> = mutableMapOf()

        fun showEnableLocationActivity(context: Context, identifier: String): CompletableDeferred<Boolean> {
            val intent = Intent(context, EnableLocationActivity::class.java).apply {
                putExtra(EXTRA_CALLBACK_ID, identifier)
                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            }

            val completableDeferred = enablingHandlers.getOrPut(identifier) { CompletableDeferred() }
            context.startActivity(intent)
            return completableDeferred
        }
    }

    private val enableResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        complete(result.resultCode == Activity.RESULT_OK)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        supportActionBar?.hide() // windowActionBar=false
        supportActionBar?.elevation = 0F // windowContentOverlay=@null

        enableResult.launch(Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS))
    }

    private fun complete(success: Boolean) {
        intent.getStringExtra(EXTRA_CALLBACK_ID)?.let {
            enablingHandlers[it]?.complete(success)
            enablingHandlers.remove(it)
        }
        finish()
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.location

import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Looper
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.splendo.kaluga.permissions.location.LocationPermission
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf

class GoogleLocationProvider(private val context: Context) : LocationProvider {

    sealed class FusedLocationProviderClientType(
        permission: LocationPermission,
        protected val context: Context
    ) {
        protected val fusedLocationProviderClient = FusedLocationProviderClient(context)
        protected val locationRequest = LocationRequest.create().setInterval(1).setMaxWaitTime(1000).setFastestInterval(1).setPriority(
            if (permission.precise) LocationRequest.PRIORITY_HIGH_ACCURACY else LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY
        )
        protected val locationsState: MutableStateFlow<List<Location>> = MutableStateFlow(emptyList())
        val locations: Flow<List<Location>> = locationsState

        abstract fun startRequestingUpdates()
        abstract fun stopRequestingUpdates()

        class Foreground(permission: LocationPermission, context: Context) : FusedLocationProviderClientType(permission, context) {

            private val locationCallback = object : LocationCallback() {

                override fun onLocationResult(locationResult: LocationResult?) {
                    super.onLocationResult(locationResult)

                    locationResult?.let {
                        locationsState.value = it.toKnownLocations()
                    }
                }
            }

            @SuppressLint("MissingPermission")
            override fun startRequestingUpdates() {
                fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
            }

            override fun stopRequestingUpdates() {
                fusedLocationProviderClient.removeLocationUpdates(locationCallback)
            }
        }

        class Background(permission: LocationPermission, context: Context) : FusedLocationProviderClientType(permission, context) {

            private val identifier = hashCode().toString()

            companion object {
                val updatingLocationInBackgroundManagers: MutableMap<String, MutableStateFlow<List<Location>>> = mutableMapOf()
            }

            private val locationUpdatedPendingIntent = GoogleLocationUpdatesBroadcastReceiver.intent(context, identifier)

            @SuppressLint("MissingPermission")
            override fun startRequestingUpdates() {
                updatingLocationInBackgroundManagers[identifier] = locationsState
                fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationUpdatedPendingIntent)
            }

            override fun stopRequestingUpdates() {
                updatingLocationInBackgroundManagers.remove(identifier)
                fusedLocationProviderClient.removeLocationUpdates(locationUpdatedPendingIntent)
            }
        }
    }

    private val fusedLocationProviderClients: MutableStateFlow<Map<LocationPermission, FusedLocationProviderClientType>> = MutableStateFlow(
        mapOf()
    )

    override fun location(permission: LocationPermission): Flow<List<Location>> = fusedLocationProviderClients.flatMapLatest {
        it[permission]?.locations ?: flowOf(emptyList())
    }

    override fun startMonitoringLocation(permission: LocationPermission) {
        val fusedLocationProviderClient = getLocationProviderForPermission(permission)
        fusedLocationProviderClient.startRequestingUpdates()
    }

    override fun stopMonitoringLocation(permissions: LocationPermission) {
        fusedLocationProviderClients.value[permissions]?.startRequestingUpdates()
        fusedLocationProviderClients.value = fusedLocationProviderClients.value.toMutableMap().apply { remove(permissions) }
    }

    private fun getLocationProviderForPermission(permission: LocationPermission): FusedLocationProviderClientType {
        return fusedLocationProviderClients.value[permission] ?: run {
            val fusedLocationProviderClient = if (permission.background) {
                FusedLocationProviderClientType.Background(permission, context)
            } else {
                FusedLocationProviderClientType.Foreground(permission, context)
            }

            fusedLocationProviderClients.value = fusedLocationProviderClients.value.toMutableMap().apply {
                put(permission, fusedLocationProviderClient)
            }
            fusedLocationProviderClient
        }
    }
}

class GoogleLocationUpdatesBroadcastReceiver : BroadcastReceiver() {

    companion object {
        private const val ACTION_NAME = "com.splendo.kaluga.location.locationupdates.action"

        fun intent(context: Context, locationManagerId: String): PendingIntent {
            val intent = Intent(context, GoogleLocationUpdatesBroadcastReceiver::class.java).apply {
                action = ACTION_NAME
                addCategory(locationManagerId)
            }
            return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        }
    }

    override fun onReceive(context: Context, intent: Intent?) {
        if (intent == null) return
        val locationResult = LocationResult.extractResult(intent) ?: return
        intent.categories.forEach { category ->
            GoogleLocationProvider.FusedLocationProviderClientType.Background.updatingLocationInBackgroundManagers[category]?.value = locationResult.toKnownLocations()
        }
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.location

import android.os.Build
import com.google.android.gms.location.LocationResult

fun android.location.Location.toKnownLocation(): Location.KnownLocation {
    return Location.KnownLocation(
        latitude = latitude,
        longitude = longitude,
        altitude = altitude,
        horizontalAccuracy = accuracy.toDouble(),
        verticalAccuracy = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) verticalAccuracyMeters.toDouble() else 0.0,
        speed = speed.toDouble(),
        time = Location.Time.MeasuredTime(time)
    )
}

fun LocationResult.toKnownLocations(): List<Location.KnownLocation> {
    return locations.mapNotNull {
        it.toKnownLocation()
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.location

import android.content.Context
import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.base.ApplicationHolder
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.location.LocationPermission
import com.splendo.kaluga.permissions.location.registerLocationPermission
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext

actual class LocationManager(
    private val context: Context,
    locationManager: android.location.LocationManager?,
    locationPermission: LocationPermission,
    permissions: Permissions,
    private val locationProvider: LocationProvider,
    autoRequestPermission: Boolean,
    autoEnableLocations: Boolean,
    locationStateRepo: LocationStateRepo
) : BaseLocationManager(locationPermission, permissions, autoRequestPermission, autoEnableLocations, locationStateRepo) {

    class Builder(
        private val context: Context = ApplicationHolder.applicationContext,
        private val locationManager: android.location.LocationManager? = context.getSystemService(Context.LOCATION_SERVICE) as? android.location.LocationManager,
        private val locationProvider: LocationProvider = GoogleLocationProvider(context)
    ) : BaseLocationManager.Builder {

        override fun create(
            locationPermission: LocationPermission,
            permissions: Permissions,
            autoRequestPermission: Boolean,
            autoEnableLocations: Boolean,
            locationStateRepo: LocationStateRepo
        ): BaseLocationManager {
            return LocationManager(context, locationManager, locationPermission, permissions, locationProvider, autoRequestPermission, autoEnableLocations, locationStateRepo)
        }
    }

    override val locationMonitor: LocationMonitor = LocationMonitor.Builder(context, locationManager).create()
    private val monitoringLocationJob: AtomicReference<Job?> = AtomicReference(null)

    public override suspend fun requestLocationEnable() {
        EnableLocationActivity.showEnableLocationActivity(context, hashCode().toString()).await()
    }

    override suspend fun startMonitoringLocation() {
        if (monitoringLocationJob.get() != null) return // optimization to skip making a job

        val job = Job(this.coroutineContext[Job])

        if (monitoringLocationJob.compareAndSet(null, job)) {
            locationProvider.startMonitoringLocation(locationPermission)
            launch(job) {
                locationProvider.location(locationPermission).collect {
                    handleLocationChanged(it)
                }
            }
        }
    }

    override suspend fun stopMonitoringLocation() {
        monitoringLocationJob.get()?.let {
            if (monitoringLocationJob.compareAndSet(it, null)) {
                locationProvider.stopMonitoringLocation(locationPermission)
                it.cancel()
            }
        }
    }
}

actual class LocationStateRepoBuilder(
    private val context: Context = ApplicationHolder.applicationContext,
    private val permissions: Permissions = Permissions(
        PermissionsBuilder(PermissionContext(context)).apply { registerLocationPermission() },
        Dispatchers.Main
    )
) : LocationStateRepo.Builder {

    override fun create(locationPermission: LocationPermission, autoRequestPermission: Boolean, autoEnableLocations: Boolean, coroutineContext: CoroutineContext): LocationStateRepo {
        return LocationStateRepo(locationPermission, permissions, autoRequestPermission, autoEnableLocations, LocationManager.Builder(context), coroutineContext)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.location

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.location.LocationManager
import androidx.core.location.LocationManagerCompat
import com.splendo.kaluga.base.ApplicationHolder
import com.splendo.kaluga.base.DefaultServiceMonitor
import com.splendo.kaluga.base.ServiceMonitor

actual interface LocationMonitor : ServiceMonitor {

    actual class Builder(
        private val applicationContext: Context = ApplicationHolder.applicationContext,
        private val locationManager: LocationManager? = applicationContext.getSystemService(Context.LOCATION_SERVICE) as? LocationManager
    ) {
        actual fun create(): LocationMonitor = DefaultLocationMonitor(
            applicationContext = applicationContext,
            locationManager = locationManager
        )
    }
}

class DefaultLocationMonitor(
    private val applicationContext: Context,
    private val locationManager: LocationManager?
) : DefaultServiceMonitor(), LocationMonitor {

    private val locationAvailabilityBroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (intent?.action == LocationManager.MODE_CHANGED_ACTION) {
                updateState()
            }
        }
    }

    override val isServiceEnabled: Boolean
        get() = if (locationManager == null) {
            false
        } else {
            LocationManagerCompat.isLocationEnabled(locationManager)
        }

    override fun startMonitoring() {
        super.startMonitoring()
        applicationContext.registerReceiver(
            locationAvailabilityBroadcastReceiver,
            IntentFilter(LocationManager.MODE_CHANGED_ACTION)
        )
    }

    override fun stopMonitoring() {
        super.stopMonitoring()
        applicationContext.unregisterReceiver(locationAvailabilityBroadcastReceiver)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.location

import com.splendo.kaluga.permissions.location.LocationPermission
import kotlinx.coroutines.flow.Flow

interface LocationProvider {
    fun location(permission: LocationPermission): Flow<List<Location>>
    fun startMonitoringLocation(permission: LocationPermission)
    fun stopMonitoringLocation(permissions: LocationPermission)
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.location

import kotlin.math.absoluteValue

sealed class Location {

    /**
     * [ms] holds the unixtime (ms since 1970) for when the location was detected. Either a time provided by the location framework,
     * or a time guessed upon receival by the implementation.
     */
    sealed class Time(open val ms: Long) {
        data class MeasuredTime(override val ms: Long) : Time(ms)
        data class GuessedTime(override val ms: Long) : Time(ms)
    }

    /**
     * A known location, [latitude], [longitude] and [time] are always available, the rest are optional
     */
    data class KnownLocation(
        val latitude: Double,
        val longitude: Double,
        val altitude: Double? = null,
        val horizontalAccuracy: Double? = null,
        val verticalAccuracy: Double? = null,
        val speed: Double? = null,
        val course: Double? = null,
        val time: Time
    ) : Location() {

        /**
         * The [DMSCoordinate] associated with the latitude of this location
         */
        val latitudeDMS: DMSCoordinate = DMSCoordinate.fromLatitude(latitude)

        /**
         * The [DMSCoordinate] associated with the latitude of this longitude
         */
        val longitudeDMS: DMSCoordinate = DMSCoordinate.fromLongitude(longitude)
    }

    /**
     * An unknown location.
     * @param reason The [Reason] the location is unknown.
     */
    sealed class UnknownLocation(open val reason: Reason) : Location() {

        enum class Reason {
            PERMISSION_DENIED,
            NO_GPS,
            NOT_CLEAR
        }

        /**
         * The current location is unknown, and there is no last known location
         * @param reason The [Reason] the location is unknown.
         */
        data class WithoutLastLocation(override val reason: Reason) : UnknownLocation(reason)

        /**
         * The current location is unknown, but there is a last known location
         * @param lastKnownLocation The [KnownLocation] last received before the location became unknown.
         * @param reason The [Reason] the location is unknown.
         */
        data class WithLastLocation(val lastKnownLocation: KnownLocation, override val reason: Reason) : UnknownLocation(reason)
    }
}

/**
 * A Location coordinate defined by degrees, minutes and seconds.
 * @param degrees The degrees of the coordinate
 * @param minutes The minutes of the coordinate
 * @param seconds The seconds of the coordinate
 * @param windDirection The [WindDirection] of the coordinate
 */
data class DMSCoordinate(val degrees: Int, val minutes: Int, val seconds: Double, val windDirection: WindDirection) {

    enum class WindDirection {
        North,
        West,
        South,
        East
    }

    companion object {

        /**
         * Converts a latitude coordinate to its [DMSCoordinate]
         */
        fun fromLatitude(latitude: Double): DMSCoordinate {
            val windDirection = if (latitude >= 0.0) WindDirection.North else WindDirection.South
            return fromDecimalDegrees(latitude, windDirection)
        }

        /**
         * Converts a longitude coordinate to its [DMSCoordinate]
         */
        fun fromLongitude(longitude: Double): DMSCoordinate {
            val windDirection = if (longitude >= 0.0) WindDirection.East else WindDirection.West
            return fromDecimalDegrees(longitude, windDirection)
        }

        private fun fromDecimalDegrees(decimalDegrees: Double, windDirection: WindDirection): DMSCoordinate {
            val degrees = decimalDegrees.absoluteValue.toInt()
            val minutesWithRemainder = (decimalDegrees - degrees) * 60
            val minutes = minutesWithRemainder.toInt()
            val seconds = ((minutesWithRemainder - minutes) * 60)
            return DMSCoordinate(degrees, minutes, seconds, windDirection)
        }
    }

    /**
     * Decimal representation of this coordinate
     */
    val decimalDegrees: Double get() {
        val sign = when (windDirection) {
            WindDirection.North, WindDirection.East -> 1.0
            WindDirection.South, WindDirection.West -> -1.0
        }
        return sign * degrees.toDouble() * (minutes.toDouble() / 60.0) * (seconds / 3600.0)
    }

    override fun toString(): String {
        return "$degrees°${minutes}\'${seconds}\" ${windDirection.name}"
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.location

import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.base.flow.filterOnlyImportant
import com.splendo.kaluga.permissions.PermissionState
import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.permissions.location.LocationPermission
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch

abstract class BaseLocationManager(
    protected val locationPermission: LocationPermission,
    private val permissions: Permissions,
    private val autoRequestPermission: Boolean,
    internal val autoEnableLocations: Boolean,
    private val locationStateRepo: LocationStateRepo
) : CoroutineScope by locationStateRepo {

    interface Builder {
        fun create(locationPermission: LocationPermission, permissions: Permissions, autoRequestPermission: Boolean, autoEnableLocations: Boolean, locationStateRepo: LocationStateRepo): BaseLocationManager
    }

    private val locationPermissionRepo get() = permissions[locationPermission]
    abstract val locationMonitor: LocationMonitor
    private var monitoringPermissionsJob: AtomicReference<Job?> = AtomicReference(null)
    private val _monitoringLocationEnabledJob: AtomicReference<Job?> = AtomicReference(null)
    private var monitoringLocationEnabledJob: Job?
        get() = _monitoringLocationEnabledJob.get()
        set(value) { _monitoringLocationEnabledJob.set(value) }

    internal open fun startMonitoringPermissions() {
        if (monitoringPermissionsJob.get() != null) return // optimization to skip making a job

        val job = Job(this.coroutineContext[Job])

        if (monitoringPermissionsJob.compareAndSet(null, job))
            launch(job) {
                locationPermissionRepo.collect { state ->
                    if (state is PermissionState.Denied.Requestable && autoRequestPermission)
                        state.request(permissions.getManager(locationPermission))
                    val hasPermission = state is PermissionState.Allowed

                    locationStateRepo.takeAndChangeState { locationState ->
                        when (locationState) {
                            is LocationState.Disabled.NoGPS, is LocationState.Enabled -> if (hasPermission) locationState.remain() else (locationState as LocationState.Permitted).revokePermission
                            is LocationState.Disabled.NotPermitted -> if (hasPermission) locationState.permit(isLocationEnabled()) else locationState.remain()
                        }
                    }
                }
            }
        // else job.cancel() <-- not needed since the job is just garbage collected and never started anything.
    }

    internal open fun stopMonitoringPermissions() {
        monitoringPermissionsJob.get()?.let {
            if (monitoringPermissionsJob.compareAndSet(it, null))
                it.cancel()
        }
    }

    internal suspend fun isPermitted(): Boolean {
        return locationPermissionRepo.filterOnlyImportant().first() is PermissionState.Allowed
    }

    internal open suspend fun startMonitoringLocationEnabled() {
        locationMonitor.startMonitoring()
        if (monitoringLocationEnabledJob != null)
            return
        monitoringLocationEnabledJob = launch {
            locationMonitor.isEnabled.collect {
                handleLocationEnabledChanged()
            }
        }
    }
    internal open fun stopMonitoringLocationEnabled() {
        locationMonitor.stopMonitoring()
        monitoringLocationEnabledJob?.cancel()
        monitoringLocationEnabledJob = null
    }
    internal fun isLocationEnabled(): Boolean = locationMonitor.isServiceEnabled
    internal abstract suspend fun requestLocationEnable()

    private suspend fun handleLocationEnabledChanged() {
        locationStateRepo.takeAndChangeState { state ->
            when (state) {
                is LocationState.Disabled.NoGPS -> if (isLocationEnabled()) state.enable else state.remain()
                is LocationState.Disabled.NotPermitted -> state.remain()
                is LocationState.Enabled -> if (isLocationEnabled()) state.remain() else state.disable
            }
        }
    }

    internal abstract suspend fun startMonitoringLocation()
    internal abstract suspend fun stopMonitoringLocation()

    internal fun handleLocationChanged(locations: List<Location>) {
        launch {
            locations.forEach { location ->
                handleLocationChanged(location)
            }
        }
    }

    internal suspend fun handleLocationChanged(location: Location) {
        locationStateRepo.takeAndChangeState { state ->
            when (state) {
                is LocationState.Disabled.NoGPS -> {
                    { state.copy(location = location.unknownLocationOf(Location.UnknownLocation.Reason.NO_GPS)) }
                }
                is LocationState.Disabled.NotPermitted -> {
                    { state.copy(location = location.unknownLocationOf(Location.UnknownLocation.Reason.PERMISSION_DENIED)) }
                }
                is LocationState.Enabled -> {
                    { state.copy(location = location) }
                }
            }
        }
    }
}

/**
 * A manager for tracking the user's [Location]
 */
expect class LocationManager : BaseLocationManager

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.location

import com.splendo.kaluga.base.ServiceMonitor

expect interface LocationMonitor : ServiceMonitor {
    class Builder {
        fun create(): LocationMonitor
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.location

import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.permissions.location.LocationPermission
import com.splendo.kaluga.state.ColdStateRepo
import com.splendo.kaluga.state.HandleAfterNewStateIsSet
import com.splendo.kaluga.state.HandleBeforeOldStateIsRemoved
import com.splendo.kaluga.state.State
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlin.coroutines.CoroutineContext

/**
 * State of a [LocationStateRepo]
 * @param location The [Location] associated with the state.
 * @param locationManager The [BaseLocationManager] managing the location state
 */
sealed class LocationState(open val location: Location, private val locationManager: BaseLocationManager) : State() {

    override suspend fun initialState() {
        locationManager.startMonitoringPermissions()
    }

    override suspend fun finalState() {
        locationManager.stopMonitoringPermissions()
    }

    interface Permitted : HandleBeforeOldStateIsRemoved<LocationState>, HandleAfterNewStateIsSet<LocationState> {
        val revokePermission: suspend () -> Disabled.NotPermitted
        suspend fun initialState()
        suspend fun finalState()
    }

    class PermittedHandler(val location: Location, private val locationManager: BaseLocationManager) : Permitted {

        override val revokePermission: suspend () -> Disabled.NotPermitted = {
            Disabled.NotPermitted(location.unknownLocationOf(Location.UnknownLocation.Reason.PERMISSION_DENIED), locationManager)
        }

        override suspend fun afterNewStateIsSet(newState: LocationState) {
            when (newState) {
                is Disabled.NotPermitted -> locationManager.stopMonitoringLocationEnabled()
                else -> {}
            }
        }

        override suspend fun beforeOldStateIsRemoved(oldState: LocationState) {
            when (oldState) {
                is Disabled.NotPermitted -> locationManager.startMonitoringLocationEnabled()
                else -> {}
            }
        }

        override suspend fun initialState() {
            locationManager.startMonitoringLocationEnabled()
        }

        override suspend fun finalState() {
            locationManager.stopMonitoringLocationEnabled()
        }
    }

    /**
     * A [LocationState] that is not actively fetching new [Location]s.
     */
    sealed class Disabled(location: Location, locationManager: BaseLocationManager) : LocationState(location, locationManager) {

        /**
         * A [LocationState.Disabled] that was disabled due to missing permissions.
         */
        data class NotPermitted(override val location: Location, private val locationManager: BaseLocationManager) : Disabled(location, locationManager) {

            /**
             * Transforms this state into [LocationState] that has sufficient permissions
             * @param enabled `true` if GPS is turned on, `false` otherwise.
             */
            fun permit(enabled: Boolean): suspend () -> LocationState = {
                if (enabled) Enabled(location, locationManager) else NoGPS(location.unknownLocationOf(Location.UnknownLocation.Reason.NO_GPS), locationManager)
            }
        }

        /**
         * A [LocationState.Disabled] that was disabled due to GPS being turned off.
         */
        data class NoGPS(override val location: Location, private val locationManager: BaseLocationManager) : Disabled(location, locationManager), Permitted {

            private val permittedHandler = PermittedHandler(location, locationManager)

            /**
             * Transforms this state into a [LocationState.Disabled.NotPermitted] state.
             */
            override val revokePermission: suspend () -> NotPermitted = permittedHandler.revokePermission

            /**
             * Transforms this state into a [LocationState.Enabled] state.
             */
            val enable: suspend () -> Enabled = {
                Enabled(location, locationManager)
            }

            override suspend fun beforeOldStateIsRemoved(oldState: LocationState) {
                permittedHandler.beforeOldStateIsRemoved(oldState)
                when (oldState) {
                    !is NoGPS -> if (locationManager.autoEnableLocations) locationManager.requestLocationEnable()
                    else -> {}
                }
            }

            override suspend fun afterNewStateIsSet(newState: LocationState) {
                permittedHandler.afterNewStateIsSet(newState)
            }

            override suspend fun initialState() {
                super.initialState()
                permittedHandler.initialState()
                if (locationManager.autoEnableLocations) locationManager.requestLocationEnable()
            }

            override suspend fun finalState() {
                super.finalState()
                permittedHandler.finalState()
            }
        }
    }

    /**
     * A [LocationState] that is actively updating its [Location].
     */
    data class Enabled(override val location: Location, private val locationManager: BaseLocationManager) : LocationState(location, locationManager), Permitted {

        private val permittedHandler = PermittedHandler(location, locationManager)

        /**
         * Transforms this state into a [LocationState.Disabled.NotPermitted] state.
         */
        override val revokePermission: suspend () -> Disabled.NotPermitted = permittedHandler.revokePermission

        /**
         * Transforms this state into a [LocationState.Disabled.NoGPS] state.
         */
        val disable: suspend () -> Disabled.NoGPS = {
            Disabled.NoGPS(location.unknownLocationOf(Location.UnknownLocation.Reason.NO_GPS), locationManager)
        }

        override suspend fun afterNewStateIsSet(newState: LocationState) {
            permittedHandler.afterNewStateIsSet(newState)
            when (newState) {
                !is Enabled -> locationManager.stopMonitoringLocation()
                else -> {}
            }
        }

        override suspend fun beforeOldStateIsRemoved(oldState: LocationState) {
            permittedHandler.beforeOldStateIsRemoved(oldState)
            when (oldState) {
                !is Enabled -> locationManager.startMonitoringLocation()
                else -> {}
            }
        }

        override suspend fun initialState() {
            super.initialState()
            permittedHandler.initialState()
            locationManager.startMonitoringLocation()
        }

        override suspend fun finalState() {
            super.finalState()
            permittedHandler.finalState()
            locationManager.stopMonitoringLocation()
        }
    }
}

/**
 * A [ColdStateRepo] that tracks the [LocationState] of the user.
 * Since this is a coldStateRepo location changes will only be requested when there is at least one observer.
 * @param locationPermission The [Permission.Location] to define the type of location state to track.
 * @param autoRequestPermission If 'true` the user will automatically receive a request to provide permissions when missing. Set this to `false` if manual permission requests are required.
 * @param autoEnableLocations If `true` the user will automatically receive a request to enable GPS if it is disabled. Set this to `false` if manual gps enabling is required.
 * @param locationManagerBuilder The [BaseLocationManager.Builder] to create the [LocationManager] managing the location state.
 */
class LocationStateRepo(
    locationPermission: LocationPermission,
    permissions: Permissions,
    autoRequestPermission: Boolean,
    autoEnableLocations: Boolean,
    locationManagerBuilder: BaseLocationManager.Builder,
    coroutineContext: CoroutineContext
) : ColdStateRepo<LocationState>(coroutineContext = coroutineContext) {

    /**
     * Builder for creating a [LocationStateRepo]
     */
    interface Builder {

        /**
         * Creates a [LocationStateRepo]
         * @param locationPermission The [Permission.Location] to define the type of location state to track.
         * @param autoRequestPermission If 'true` the user will automatically receive a request to provide permissions when missing. Set this to `false` if manual permission requests are required. Defaults to `true`.
         * @param autoEnableLocations If `true` the user will automatically receive a request to enable GPS if it is disabled. Set this to `false` if manual gps enabling is required. Defaults to `true`.
         * @return The created [LocationStateRepo]
         */
        fun create(
            locationPermission: LocationPermission,
            autoRequestPermission: Boolean = true,
            autoEnableLocations: Boolean = true,
            coroutineContext: CoroutineContext = Dispatchers.Main
        ): LocationStateRepo
    }
    private var _lastKnownLocation = AtomicReference<Location>(Location.UnknownLocation.WithoutLastLocation(Location.UnknownLocation.Reason.NOT_CLEAR))
    private var lastKnownLocation: Location
        get() = _lastKnownLocation.get()
        set(value) { _lastKnownLocation.set(value) }

    private val locationManager = locationManagerBuilder.create(locationPermission, permissions, autoRequestPermission, autoEnableLocations, this)

    override suspend fun initialValue(): LocationState {
        return if (!locationManager.isPermitted()) {
            LocationState.Disabled.NotPermitted(lastKnownLocation.unknownLocationOf(Location.UnknownLocation.Reason.PERMISSION_DENIED), locationManager)
        } else if (!locationManager.isLocationEnabled()) {
            LocationState.Disabled.NoGPS(lastKnownLocation.unknownLocationOf(Location.UnknownLocation.Reason.NO_GPS), locationManager)
        } else {
            LocationState.Enabled(lastKnownLocation, locationManager)
        }
    }

    override suspend fun deinitialize(state: LocationState) {
        lastKnownLocation = state.location
    }
}

expect class LocationStateRepoBuilder : LocationStateRepo.Builder

fun Location.unknownLocationOf(reason: Location.UnknownLocation.Reason): Location {
    return when (this) {
        is Location.KnownLocation -> Location.UnknownLocation.WithLastLocation(this, reason)
        is Location.UnknownLocation.WithLastLocation -> Location.UnknownLocation.WithLastLocation(this.lastKnownLocation, reason)
        is Location.UnknownLocation.WithoutLastLocation -> Location.UnknownLocation.WithoutLastLocation(reason)
    }
}

/**
 * Transforms a [Flow] of [LocationState] into a flow of its associated [Location]
 */
fun Flow<LocationState>.location(): Flow<Location> {
    return this.map { it.location }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.location

import kotlinx.cinterop.useContents
import platform.CoreLocation.CLLocation
import platform.Foundation.timeIntervalSince1970

val CLLocation.knownLocation
    get() = coordinate.useContents {
        Location.KnownLocation(
            latitude = latitude,
            longitude = longitude,
            altitude = altitude,
            horizontalAccuracy = horizontalAccuracy,
            verticalAccuracy = verticalAccuracy,
            course = course,
            speed = speed,
            time = Location.Time.MeasuredTime(timestamp.timeIntervalSince1970.toLong() * 1_000L)
        )
    }

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.location

import co.touchlab.stately.concurrency.AtomicBoolean
import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.location.LocationPermission
import com.splendo.kaluga.permissions.location.registerLocationPermission
import kotlinx.coroutines.Dispatchers
import platform.CoreLocation.CLLocation
import platform.CoreLocation.CLLocationManager
import platform.CoreLocation.CLLocationManagerDelegateProtocol
import platform.Foundation.NSBundle
import platform.Foundation.NSError
import platform.darwin.NSObject
import kotlin.coroutines.CoroutineContext

actual class LocationManager(
    private val locationManager: CLLocationManager,
    locationPermission: LocationPermission,
    permissions: Permissions,
    autoRequestPermission: Boolean,
    autoEnableLocations: Boolean,
    locationStateRepo: LocationStateRepo
) : BaseLocationManager(locationPermission, permissions, autoRequestPermission, autoEnableLocations, locationStateRepo) {

    class Builder(private val locationManager: CLLocationManager = CLLocationManager()) : BaseLocationManager.Builder {

        override fun create(
            locationPermission: LocationPermission,
            permissions: Permissions,
            autoRequestPermission: Boolean,
            autoEnableLocations: Boolean,
            locationStateRepo: LocationStateRepo
        ): BaseLocationManager = LocationManager(
            locationManager,
            locationPermission,
            permissions,
            autoRequestPermission,
            autoEnableLocations,
            locationStateRepo
        )
    }

    private val locationManagerDelegate = object : NSObject(), CLLocationManagerDelegateProtocol {

        override fun locationManager(manager: CLLocationManager, didUpdateLocations: List<*>) {
            if (isMonitoringLocationUpdate) {
                val locations = didUpdateLocations.mapNotNull { (it as? CLLocation)?.knownLocation }
                if (locations.isNotEmpty()) {
                    handleLocationChanged(locations)
                }
            }
        }

        override fun locationManager(manager: CLLocationManager, didUpdateToLocation: CLLocation, fromLocation: CLLocation) {
            if (isMonitoringLocationUpdate) {
                handleLocationChanged(listOf(didUpdateToLocation.knownLocation))
            }
        }

        override fun locationManager(manager: CLLocationManager, didFinishDeferredUpdatesWithError: NSError?) {
        }
    }

    override val locationMonitor: LocationMonitor = LocationMonitor.Builder(CLLocationManager()).create()

    private var _isMonitoringLocationUpdate = AtomicBoolean(false)
    var isMonitoringLocationUpdate
        get() = _isMonitoringLocationUpdate.value
        set(value) { _isMonitoringLocationUpdate.value = value }

    override suspend fun requestLocationEnable() {
        // No access to UIApplication.openSettingsURLString
        // We have to fallback to alert then?
    }

    override suspend fun startMonitoringLocation() {
        isMonitoringLocationUpdate = true
        locationManager.delegate = locationManagerDelegate
        locationManager.startUpdatingLocation()
    }

    override suspend fun stopMonitoringLocation() {
        locationManager.stopUpdatingLocation()
        isMonitoringLocationUpdate = false
    }
}

actual class LocationStateRepoBuilder(
    private val bundle: NSBundle = NSBundle.mainBundle,
    private val locationManager: CLLocationManager = CLLocationManager(),
    private val permissions: Permissions = Permissions(
        PermissionsBuilder(bundle).apply {
            registerLocationPermission()
        },
        Dispatchers.Main
    )
) : LocationStateRepo.Builder {

    override fun create(
        locationPermission: LocationPermission,
        autoRequestPermission: Boolean,
        autoEnableLocations: Boolean,
        coroutineContext: CoroutineContext
    ): LocationStateRepo {
        return LocationStateRepo(
            locationPermission,
            permissions,
            autoRequestPermission,
            autoEnableLocations,
            LocationManager.Builder(locationManager),
            coroutineContext
        )
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.location

import com.splendo.kaluga.base.DefaultServiceMonitor
import com.splendo.kaluga.base.ServiceMonitor
import platform.CoreLocation.CLLocationManager
import platform.CoreLocation.CLLocationManagerDelegateProtocol
import platform.darwin.NSObject

actual interface LocationMonitor : ServiceMonitor {
    actual class Builder(
        val locationManager: CLLocationManager = CLLocationManager()
    ) {
        actual fun create(): LocationMonitor = DefaultLocationMonitor(
            locationManager = locationManager
        )
    }
}

class DefaultLocationMonitor(private val locationManager: CLLocationManager) : DefaultServiceMonitor(), LocationMonitor {

    internal class LocationManagerDelegate(
        private val updateState: () -> Unit
    ) : NSObject(), CLLocationManagerDelegateProtocol {
        override fun locationManagerDidChangeAuthorization(manager: CLLocationManager) {
            updateState()
        }
    }

    override val isServiceEnabled: Boolean
        get() = locationManager.locationServicesEnabled()

    override fun startMonitoring() {
        super.startMonitoring()
        locationManager.delegate = LocationManagerDelegate(::updateState)
    }

    override fun stopMonitoring() {
        super.stopMonitoring()
        locationManager.delegate = null
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.permissions.microphone.av

import com.splendo.kaluga.permissions.av.AVType
import com.splendo.kaluga.permissions.microphone.MicrophonePermission
import com.splendo.kaluga.permissions.microphone.MicrophonePermissionManager
import platform.AVFoundation.AVMediaTypeAudio

const val NSMicrophoneUsageDescription = "NSMicrophoneUsageDescription"

class AVTypeMicrophone(override val permissionManager: MicrophonePermissionManager) : AVType<MicrophonePermission>() {

    override val avMediaType = AVMediaTypeAudio
    override val declarationName = NSMicrophoneUsageDescription
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.view

import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.view.View
import com.splendo.kaluga.resources.dpToPixel
import com.splendo.kaluga.resources.stylable.BackgroundStyle
import com.splendo.kaluga.resources.stylable.GradientStyle

fun BackgroundStyle.createDrawable(context: Context): Drawable {
    return GradientDrawable().apply {
        applyShape(this@createDrawable.shape, context)
        applyFillStyle(fillStyle, context)
        applyStrokeStyle(strokeStyle, context)
    }
}

private fun GradientDrawable.applyShape(shape: BackgroundStyle.Shape, context: Context) {
    this.shape = when (shape) {
        is BackgroundStyle.Shape.Rectangle -> {
            val radiusX = shape.cornerRadiusX.dpToPixel(context)
            val radiusY = shape.cornerRadiusY.dpToPixel(context)
            cornerRadii = floatArrayOf(
                if (shape.roundedCorners.contains(BackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT)) radiusX else 0.0f,
                if (shape.roundedCorners.contains(BackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT)) radiusY else 0.0f,
                if (shape.roundedCorners.contains(BackgroundStyle.Shape.Rectangle.Corner.TOP_RIGHT)) radiusX else 0.0f,
                if (shape.roundedCorners.contains(BackgroundStyle.Shape.Rectangle.Corner.TOP_RIGHT)) radiusY else 0.0f,
                if (shape.roundedCorners.contains(BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_RIGHT)) radiusX else 0.0f,
                if (shape.roundedCorners.contains(BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_RIGHT)) radiusY else 0.0f,
                if (shape.roundedCorners.contains(BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_LEFT)) radiusX else 0.0f,
                if (shape.roundedCorners.contains(BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_LEFT)) radiusY else 0.0f,
            )
            GradientDrawable.RECTANGLE
        }
        is BackgroundStyle.Shape.Oval -> GradientDrawable.OVAL
    }
}

private fun GradientDrawable.applyFillStyle(fillStyle: BackgroundStyle.FillStyle, context: Context) {
    when (fillStyle) {
        is BackgroundStyle.FillStyle.Solid -> {
            color = ColorStateList(arrayOf(intArrayOf()), intArrayOf(fillStyle.color))
        }
        is BackgroundStyle.FillStyle.Gradient -> {
            val colors = fillStyle.gradientStyle.colorPoints.map { it.color }.toIntArray()
            val offsets = fillStyle.gradientStyle.colorPoints.map { it.offset }.toFloatArray()
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                setColors(
                    colors,
                    offsets
                )
            } else {
                setColors(colors)
            }
            applyGradientStyle(fillStyle.gradientStyle, context)
        }
    }
}

private fun GradientDrawable.applyGradientStyle(gradientStyle: GradientStyle, context: Context) {
    when (gradientStyle) {
        is GradientStyle.Linear -> {
            gradientType = GradientDrawable.LINEAR_GRADIENT
            orientation = when (gradientStyle.orientation) {
                GradientStyle.Linear.Orientation.TOP_BOTTOM -> GradientDrawable.Orientation.TOP_BOTTOM
                GradientStyle.Linear.Orientation.TOP_LEFT_BOTTOM_RIGHT -> GradientDrawable.Orientation.TL_BR
                GradientStyle.Linear.Orientation.TOP_RIGHT_BOTTOM_LEFT -> GradientDrawable.Orientation.TR_BL
                GradientStyle.Linear.Orientation.BOTTOM_TOP -> GradientDrawable.Orientation.BOTTOM_TOP
                GradientStyle.Linear.Orientation.BOTTOM_LEFT_TOP_RIGHT -> GradientDrawable.Orientation.BL_TR
                GradientStyle.Linear.Orientation.BOTTOM_RIGHT_TOP_LEFT -> GradientDrawable.Orientation.BR_TL
                GradientStyle.Linear.Orientation.LEFT_RIGHT -> GradientDrawable.Orientation.LEFT_RIGHT
                GradientStyle.Linear.Orientation.RIGHT_LEFT -> GradientDrawable.Orientation.RIGHT_LEFT
            }
        }
        is GradientStyle.Radial -> {
            gradientType = GradientDrawable.RADIAL_GRADIENT
            gradientRadius = gradientStyle.radius.dpToPixel(context)
            setGradientCenter(gradientStyle.centerPoint.x, gradientStyle.centerPoint.y)
        }
        is GradientStyle.Angular -> {
            gradientType = GradientDrawable.SWEEP_GRADIENT
            setGradientCenter(gradientStyle.centerPoint.x, gradientStyle.centerPoint.y)
        }
    }
}

private fun GradientDrawable.applyStrokeStyle(strokeStyle: BackgroundStyle.StrokeStyle, context: Context) {
    when (strokeStyle) {
        is BackgroundStyle.StrokeStyle.Stroke -> {
            setStroke(strokeStyle.width.dpToPixel(context).toInt(), ColorStateList(arrayOf(intArrayOf()), intArrayOf(strokeStyle.color)))
        }
        is BackgroundStyle.StrokeStyle.None -> {}
    }
}

fun View.applyBackgroundStyle(backgroundStyle: BackgroundStyle) {
    background = backgroundStyle.createDrawable(context)
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.

 */

@file:JvmName("AndroidButton")
package com.splendo.kaluga.resources.view

import android.content.res.ColorStateList
import android.graphics.drawable.RippleDrawable
import android.graphics.drawable.StateListDrawable
import android.util.StateSet
import com.splendo.kaluga.resources.Color
import com.splendo.kaluga.resources.stylable.ButtonStyle

/**
 * Binds kaluga button
 *
 * @throws IllegalArgumentException in case a unknown button type
 */
fun android.widget.Button.bindButton(button: KalugaButton) {
    text = when (button) {
        is KalugaButton.Plain -> button.text
        is KalugaButton.Styled -> button.text.spannable
    }
    applyButtonStyle(button.style)
    isAllCaps = false
    isEnabled = button.isEnabled
    setOnClickListener { button.action() }
}

sealed class RippleStyle {
    object None : RippleStyle()
    object ForegroundRipple : RippleStyle()
    data class CustomRipple(val color: Color) : RippleStyle()
}

fun android.widget.Button.applyButtonStyle(style: ButtonStyle, rippleStyle: RippleStyle = RippleStyle.ForegroundRipple) {
    typeface = style.font
    isAllCaps = false
    textSize = style.textSize
    textAlignment = style.textAlignment.alignment(context).viewAlignment
    setTextColor(
        ColorStateList(
            arrayOf(
                intArrayOf(android.R.attr.state_pressed),
                intArrayOf(-android.R.attr.state_enabled),
                intArrayOf()
            ),
            intArrayOf(
                style.pressedStyle.textColor,
                style.disabledStyle.textColor,
                style.defaultStyle.textColor
            )
        )
    )
    val stateListDrawable = StateListDrawable().apply {
        addState(
            intArrayOf(android.R.attr.state_pressed),
            style.pressedStyle.backgroundStyle.createDrawable(context)
        )
        addState(
            intArrayOf(-android.R.attr.state_enabled),
            style.disabledStyle.backgroundStyle.createDrawable(context)
        )
        addState(
            StateSet.WILD_CARD,
            style.defaultStyle.backgroundStyle.createDrawable(context)
        )
    }
    background = when (rippleStyle) {
        is RippleStyle.None -> stateListDrawable
        is RippleStyle.ForegroundRipple -> {
            RippleDrawable(
                ColorStateList(arrayOf(intArrayOf()), intArrayOf(style.pressedStyle.textColor)),
                stateListDrawable,
                null
            )
        }
        is RippleStyle.CustomRipple -> {
            RippleDrawable(
                ColorStateList(arrayOf(intArrayOf()), intArrayOf(rippleStyle.color)),
                stateListDrawable,
                null
            )
        }
    }
}

package com.splendo.kaluga.resources.view

import android.content.Context
import android.os.Build
import android.text.Layout
import android.text.TextUtils
import android.view.Gravity
import android.view.View
import com.splendo.kaluga.resources.stylable.TextAlignment

fun TextAlignment.alignment(context: Context): Layout.Alignment {
    val locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        context.resources.configuration.locales.get(0)
    } else {
        context.resources.configuration.locale
    }
    return when (this) {
        TextAlignment.LEFT -> if (TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL) {
            Layout.Alignment.ALIGN_OPPOSITE
        } else {
            Layout.Alignment.ALIGN_NORMAL
        }
        TextAlignment.RIGHT -> if (TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_LTR) {
            Layout.Alignment.ALIGN_OPPOSITE
        } else {
            Layout.Alignment.ALIGN_NORMAL
        }
        TextAlignment.CENTER -> Layout.Alignment.ALIGN_CENTER
        TextAlignment.END -> Layout.Alignment.ALIGN_OPPOSITE
        TextAlignment.START -> Layout.Alignment.ALIGN_NORMAL
    }
}

val Layout.Alignment.viewAlignment: Int get() = when (this) {
    Layout.Alignment.ALIGN_NORMAL -> View.TEXT_ALIGNMENT_TEXT_START
    Layout.Alignment.ALIGN_OPPOSITE -> View.TEXT_ALIGNMENT_TEXT_END
    Layout.Alignment.ALIGN_CENTER -> View.TEXT_ALIGNMENT_CENTER
}

val Layout.Alignment.gravity: Int get() = when (this) {
    Layout.Alignment.ALIGN_NORMAL -> Gravity.START
    Layout.Alignment.ALIGN_OPPOSITE -> Gravity.END
    Layout.Alignment.ALIGN_CENTER -> Gravity.CENTER_HORIZONTAL
}

package com.splendo.kaluga.resources.view

import android.content.res.ColorStateList
import android.text.method.LinkMovementMethod
import android.text.style.URLSpan
import android.widget.TextView
import com.splendo.kaluga.resources.stylable.TextStyle

fun TextView.bindLabel(label: KalugaLabel) {
    text = when (label) {
        is KalugaLabel.Plain -> label.text
        is KalugaLabel.Styled -> {
            if (label.text.spannable.getSpans(0, label.text.spannable.length, URLSpan::class.java).isNotEmpty()) {
                movementMethod = LinkMovementMethod.getInstance()
            }
            label.text.spannable
        }
    }
    applyTextStyle(label.style)
}

fun TextView.applyTextStyle(textStyle: TextStyle) {
    typeface = textStyle.font
    textSize = textStyle.size
    setTextColor(ColorStateList(arrayOf(intArrayOf()), intArrayOf(textStyle.color)))
    isAllCaps = false
    gravity = textStyle.alignment.alignment(context).gravity
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.stylable

import com.splendo.kaluga.resources.Color

data class BackgroundStyle(
    val fillStyle: FillStyle,
    val strokeStyle: StrokeStyle = StrokeStyle.None,
    val shape: Shape = Shape.Rectangle()
) {

    sealed class FillStyle {
        data class Solid(val color: Color) : FillStyle()
        data class Gradient(val gradientStyle: GradientStyle) : FillStyle()
    }

    sealed class Shape {
        data class Rectangle(
            val cornerRadiusX: Float,
            val cornerRadiusY: Float,
            val roundedCorners: Set<Corner> = Corner.values().toSet()
        ) : Shape() {

            enum class Corner {
                TOP_LEFT,
                TOP_RIGHT,
                BOTTOM_LEFT,
                BOTTOM_RIGHT
            }

            constructor(cornerRadius: Float = 0.0f, roundedCorners: Set<Corner> = Corner.values().toSet()) : this(cornerRadius, cornerRadius, roundedCorners)
        }
        object Oval : Shape()
    }

    sealed class StrokeStyle {
        object None : StrokeStyle()
        data class Stroke(val width: Float, val color: Color) : StrokeStyle()
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.stylable

import com.splendo.kaluga.resources.Color
import com.splendo.kaluga.resources.DefaultColors
import com.splendo.kaluga.resources.Font

data class ButtonStyle(
    val font: Font,
    val textSize: Float,
    val textAlignment: TextAlignment = TextAlignment.CENTER,
    val defaultStyle: ButtonStateStyle,
    val pressedStyle: ButtonStateStyle = defaultStyle,
    val disabledStyle: ButtonStateStyle = defaultStyle
) {
    constructor(
        textStyle: TextStyle,
        textAlignment: TextAlignment = TextAlignment.CENTER,
        backgroundColor: Color = DefaultColors.clear,
        pressedBackgroundColor: Color = backgroundColor,
        disabledBackgroundColor: Color = backgroundColor,
        shape: BackgroundStyle.Shape = BackgroundStyle.Shape.Rectangle()
    ) : this(
        textStyle.font,
        textStyle.size,
        textAlignment,
        ButtonStateStyle(
            textStyle.color,
            backgroundColor,
            shape
        ),
        ButtonStateStyle(
            textStyle.color,
            pressedBackgroundColor,
            shape
        ),
        ButtonStateStyle(
            textStyle.color,
            disabledBackgroundColor,
            shape
        )
    )

    fun getStateTextStyle(isEnabled: Boolean, isPressed: Boolean) =
        TextStyle(font, getStateStyle(isEnabled, isPressed).textColor, textSize, textAlignment)

    fun getStateStyle(isEnabled: Boolean, isPressed: Boolean): ButtonStateStyle {
        return if (!isEnabled) {
            disabledStyle
        } else if (isPressed) {
            pressedStyle
        } else {
            defaultStyle
        }
    }
}

data class ButtonStateStyle(
    val textColor: Color,
    val backgroundStyle: BackgroundStyle,
) {

    constructor(textColor: Color, backgroundColor: Color = DefaultColors.clear, shape: BackgroundStyle.Shape = BackgroundStyle.Shape.Rectangle()) : this(
        textColor,
        BackgroundStyle(
            BackgroundStyle.FillStyle.Solid(backgroundColor),
            shape = shape
        )
    )
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.stylable

import com.splendo.kaluga.resources.Color
import kotlin.jvm.JvmName

sealed class GradientStyle(val colorPoints: List<ColorPoint>) {
    data class ColorPoint(val color: Color, val offset: Float)
    data class CenterPoint(val x: Float, val y: Float)

    class Linear private constructor (colorPoints: List<ColorPoint>, val orientation: Orientation) : GradientStyle(colorPoints) {
        enum class Orientation {
            BOTTOM_LEFT_TOP_RIGHT,
            BOTTOM_TOP,
            TOP_RIGHT_BOTTOM_LEFT,
            LEFT_RIGHT,
            RIGHT_LEFT,
            TOP_LEFT_BOTTOM_RIGHT,
            TOP_BOTTOM,
            BOTTOM_RIGHT_TOP_LEFT
        }

        companion object {
            @JvmName("fromColors")
            operator fun invoke(colors: List<Color>, orientation: Orientation = Orientation.LEFT_RIGHT) = Linear(colors.colorPoints, orientation)
            @JvmName("fromColorPoints")
            operator fun invoke(colorPoints: List<ColorPoint>, orientation: Orientation = Orientation.LEFT_RIGHT) = Linear(colorPoints, orientation)
        }
    }

    class Radial(
        colorPoints: List<ColorPoint>,
        val radius: Float,
        val centerPoint: CenterPoint = CenterPoint(0.5f, 0.5f)
    ) : GradientStyle(colorPoints) {
        companion object {
            @JvmName("fromColors")
            operator fun invoke(colors: List<Color>, radius: Float, centerPoint: CenterPoint = CenterPoint(0.5f, 0.5f)) = Radial(colors.colorPoints, radius, centerPoint)
            @JvmName("fromColorPoints")
            operator fun invoke(colorPoints: List<ColorPoint>, radius: Float, centerPoint: CenterPoint = CenterPoint(0.5f, 0.5f)) = Radial(colorPoints, radius, centerPoint)
        }
    }

    class Angular(
        colorPoints: List<ColorPoint>,
        val centerPoint: CenterPoint = CenterPoint(0.5f, 0.5f)
    ) : GradientStyle(colorPoints) {
        companion object {
            @JvmName("fromColors")
            operator fun invoke(colors: List<Color>, centerPoint: CenterPoint = CenterPoint(0.5f, 0.5f)) = Angular(colors.colorPoints, centerPoint)
            @JvmName("fromColorPoints")
            operator fun invoke(colorPoints: List<ColorPoint>, centerPoint: CenterPoint = CenterPoint(0.5f, 0.5f)) = Angular(colorPoints, centerPoint)
        }
    }
}

val List<Color>.colorPoints: List<GradientStyle.ColorPoint> get() {
    if (size < 2) {
        throw Error("Gradient must have at least two colours")
    }
    val stepSize = 1.0f / (size.toFloat() - 1.0f)
    return mapIndexed { index, color -> GradientStyle.ColorPoint(color, index * stepSize) }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.stylable

enum class TextAlignment {
    LEFT,
    RIGHT,
    END,
    START,
    CENTER
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.stylable

import com.splendo.kaluga.resources.Color
import com.splendo.kaluga.resources.Font

data class TextStyle(
    val font: Font,
    val color: Color,
    val size: Float,
    val alignment: TextAlignment = TextAlignment.START
)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.view

import com.splendo.kaluga.resources.StyledString
import com.splendo.kaluga.resources.stylable.ButtonStyle

sealed class KalugaButton {
    abstract val style: ButtonStyle
    abstract val isEnabled: Boolean
    abstract val action: () -> Unit
    data class Plain(
        val text: String,
        override val style: ButtonStyle,
        override val isEnabled: Boolean = true,
        override val action: () -> Unit
    ) : KalugaButton()

    data class Styled(
        val text: StyledString,
        override val style: ButtonStyle,
        override val isEnabled: Boolean = true,
        override val action: () -> Unit
    ) : KalugaButton()
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.view

import com.splendo.kaluga.resources.StyledString
import com.splendo.kaluga.resources.stylable.TextStyle

sealed class KalugaLabel {
    abstract val style: TextStyle

    data class Plain(
        val text: String,
        override val style: TextStyle
    ) : KalugaLabel()

    data class Styled(
        val text: StyledString,
        override val style: TextStyle = text.defaultTextStyle
    ) : KalugaLabel()
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.uikit

import com.splendo.kaluga.resources.stylable.BackgroundStyle
import com.splendo.kaluga.resources.stylable.GradientStyle
import kotlinx.cinterop.CValue
import kotlinx.cinterop.useContents
import platform.CoreFoundation.CFRetain
import platform.CoreGraphics.CGColorRef
import platform.CoreGraphics.CGFloat
import platform.CoreGraphics.CGPathRef
import platform.CoreGraphics.CGPointMake
import platform.CoreGraphics.CGRect
import platform.CoreGraphics.CGSizeMake
import platform.Foundation.CFBridgingRelease
import platform.QuartzCore.CAGradientLayer
import platform.QuartzCore.CALayer
import platform.QuartzCore.CAShapeLayer
import platform.QuartzCore.kCAGradientLayerAxial
import platform.QuartzCore.kCAGradientLayerConic
import platform.QuartzCore.kCAGradientLayerRadial
import platform.UIKit.UIBezierPath
import platform.UIKit.UIColor
import platform.UIKit.UIRectCornerBottomLeft
import platform.UIKit.UIRectCornerBottomRight
import platform.UIKit.UIRectCornerTopLeft
import platform.UIKit.UIRectCornerTopRight
import platform.UIKit.UIView

fun UIView.applyBackgroundStyle(style: BackgroundStyle) = layer.applyBackgroundStyle(style, bounds)

fun CALayer.applyBackgroundStyle(style: BackgroundStyle, bounds: CValue<CGRect>) = apply {
    val maskPath = pathForShape(style.shape, bounds)
    mask = CAShapeLayer(this).apply {
        frame = bounds
        path = maskPath
    }
    applyFillStyle(style.fillStyle, bounds)
    applyStroke(style.strokeStyle, maskPath, bounds)
}

private fun pathForShape(shape: BackgroundStyle.Shape, bounds: CValue<CGRect>): CGPathRef? =
    when (shape) {
        is BackgroundStyle.Shape.Rectangle -> UIBezierPath.bezierPathWithRoundedRect(
            bounds,
            shape.roundedCorners.fold(0U) { acc, corner ->
                acc or when (corner) {
                    BackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT -> UIRectCornerTopLeft
                    BackgroundStyle.Shape.Rectangle.Corner.TOP_RIGHT -> UIRectCornerTopRight
                    BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_LEFT -> UIRectCornerBottomLeft
                    BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_RIGHT -> UIRectCornerBottomRight
                }
            },
            CGSizeMake(
                shape.cornerRadiusX.toDouble() as CGFloat,
                shape.cornerRadiusY.toDouble() as CGFloat
            )
        )
        is BackgroundStyle.Shape.Oval -> UIBezierPath.bezierPathWithOvalInRect(bounds)
    }.CGPath

private fun CALayer.applyFillStyle(fillStyle: BackgroundStyle.FillStyle, bounds: CValue<CGRect>) {
    addSublayer(
        CAGradientLayer(this).apply {
            frame = bounds
            when (fillStyle) {
                is BackgroundStyle.FillStyle.Solid -> {
                    type = kCAGradientLayerAxial
                    colors = listOfNotNull(
                        fillStyle.color.uiColor.CGColor,
                        fillStyle.color.uiColor.CGColor
                    ).mapToCGColor()
                }
                is BackgroundStyle.FillStyle.Gradient -> {
                    val sortedColorPoints =
                        fillStyle.gradientStyle.colorPoints.sortedBy { it.offset }
                    colors =
                        sortedColorPoints.mapNotNull { it.color.uiColor.CGColor }.mapToCGColor()
                    locations = sortedColorPoints.map { it.offset }
                    applyGradientStyle(fillStyle.gradientStyle, bounds)
                }
            }
        }
    )
}

fun List<CGColorRef>.mapToCGColor() = map {
    CFBridgingRelease(CFRetain(it))
}

private fun CAGradientLayer.applyGradientStyle(
    gradientStyle: GradientStyle,
    bounds: CValue<CGRect>
) = when (gradientStyle) {
    is GradientStyle.Linear -> {
        type = kCAGradientLayerAxial
        val startAndEndPoint = when (gradientStyle.orientation) {
            GradientStyle.Linear.Orientation.TOP_LEFT_BOTTOM_RIGHT -> Pair(
                CGPointMake(0.0 as CGFloat, 0.0 as CGFloat),
                CGPointMake(1.0 as CGFloat, 1.0 as CGFloat)
            )
            GradientStyle.Linear.Orientation.TOP_RIGHT_BOTTOM_LEFT -> Pair(
                CGPointMake(1.0 as CGFloat, 0.0 as CGFloat),
                CGPointMake(0.0 as CGFloat, 0.0 as CGFloat)
            )
            GradientStyle.Linear.Orientation.TOP_BOTTOM -> Pair(
                CGPointMake(0.5 as CGFloat, 0.0 as CGFloat),
                CGPointMake(0.5 as CGFloat, 1.0 as CGFloat)
            )
            GradientStyle.Linear.Orientation.LEFT_RIGHT -> Pair(
                CGPointMake(0.0 as CGFloat, 0.5 as CGFloat),
                CGPointMake(1.0 as CGFloat, 0.5 as CGFloat)
            )
            GradientStyle.Linear.Orientation.BOTTOM_LEFT_TOP_RIGHT -> Pair(
                CGPointMake(0.0 as CGFloat, 1.0 as CGFloat),
                CGPointMake(1.0 as CGFloat, 0.0 as CGFloat)
            )
            GradientStyle.Linear.Orientation.BOTTOM_RIGHT_TOP_LEFT -> Pair(
                CGPointMake(1.0 as CGFloat, 1.0 as CGFloat),
                CGPointMake(0.0 as CGFloat, 0.0 as CGFloat)
            )
            GradientStyle.Linear.Orientation.BOTTOM_TOP -> Pair(
                CGPointMake(0.5 as CGFloat, 1.0 as CGFloat),
                CGPointMake(0.5 as CGFloat, 0.0 as CGFloat)
            )
            GradientStyle.Linear.Orientation.RIGHT_LEFT -> Pair(
                CGPointMake(1.0 as CGFloat, 0.5 as CGFloat),
                CGPointMake(0.0 as CGFloat, 0.5 as CGFloat)
            )
        }
        startPoint = startAndEndPoint.first
        endPoint = startAndEndPoint.second
    }
    is GradientStyle.Radial -> {
        type = kCAGradientLayerRadial
        startPoint = CGPointMake(
            gradientStyle.centerPoint.x.toDouble() as CGFloat,
            gradientStyle.centerPoint.y.toDouble() as CGFloat
        )
        endPoint = CGPointMake(
            gradientStyle.centerPoint.x.toDouble() as CGFloat + (gradientStyle.radius / bounds.useContents { size.width }),
            gradientStyle.centerPoint.y.toDouble() as CGFloat + (gradientStyle.radius / bounds.useContents { size.height })
        )
    }
    is GradientStyle.Angular -> {
        type = kCAGradientLayerConic
        startPoint = CGPointMake(gradientStyle.centerPoint.x.toDouble() as CGFloat, gradientStyle.centerPoint.y.toDouble() as CGFloat)
        endPoint = CGPointMake(1.0 as CGFloat, gradientStyle.centerPoint.y.toDouble() as CGFloat)
    }
}

private fun CALayer.applyStroke(
    strokeStyle: BackgroundStyle.StrokeStyle,
    path: CGPathRef?,
    bounds: CValue<CGRect>
) {
    addSublayer(
        CAShapeLayer(this).apply {
            frame = bounds
            this.path = path
            when (strokeStyle) {
                is BackgroundStyle.StrokeStyle.Stroke -> {
                    lineWidth = strokeStyle.width.toDouble() as CGFloat
                    strokeColor = strokeStyle.color.uiColor.CGColor
                }
                is BackgroundStyle.StrokeStyle.None -> {
                    lineWidth = 0.0 as CGFloat
                    strokeColor = UIColor.clearColor.CGColor
                }
            }
            fillColor = null
        }
    )
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.uikit

import com.splendo.kaluga.resources.stylable.ButtonStateStyle
import com.splendo.kaluga.resources.stylable.ButtonStyle
import com.splendo.kaluga.resources.view.KalugaButton
import kotlinx.cinterop.ObjCAction
import kotlinx.cinterop.useContents
import platform.CoreGraphics.CGFloat
import platform.CoreGraphics.CGSizeMake
import platform.Foundation.NSMapTable
import platform.Foundation.NSPointerFunctionsStrongMemory
import platform.Foundation.NSPointerFunctionsWeakMemory
import platform.QuartzCore.CALayer
import platform.UIKit.UIButton
import platform.UIKit.UIControlEventTouchUpInside
import platform.UIKit.UIControlState
import platform.UIKit.UIControlStateDisabled
import platform.UIKit.UIControlStateHighlighted
import platform.UIKit.UIControlStateNormal
import platform.UIKit.UIGraphicsBeginImageContext
import platform.UIKit.UIGraphicsEndImageContext
import platform.UIKit.UIGraphicsGetCurrentContext
import platform.UIKit.UIGraphicsGetImageFromCurrentImageContext
import platform.UIKit.setFont
import platform.darwin.NSObject
import platform.objc.sel_registerName

class UIControlClosure(private val action: () -> Unit) : NSObject() {

    @ThreadLocal
    object Registry {
        val registeredUIControl = NSMapTable(NSPointerFunctionsWeakMemory, NSPointerFunctionsStrongMemory, 0)
    }

    @ObjCAction
    fun invoke() {
        action()
    }
}

fun UIButton.bindButton(button: KalugaButton) {
    applyStyle(button.style)
    when (button) {
        is KalugaButton.Plain -> setTitle(button.text, UIControlStateNormal)
        is KalugaButton.Styled -> setAttributedTitle(button.text.attributeString, UIControlStateNormal)
    }
    setEnabled(button.isEnabled)
    val wrappedAction = UIControlClosure(button.action)
    UIControlClosure.Registry.registeredUIControl.setObject(wrappedAction, this)
    addTarget(wrappedAction, sel_registerName("invoke"), UIControlEventTouchUpInside)
}

fun UIButton.applyStyle(buttonStyle: ButtonStyle) {
    setFont(buttonStyle.font.fontWithSize(buttonStyle.textSize.toDouble() as CGFloat))
    setContentHorizontalAlignment(buttonStyle.textAlignment.contentHorizontalAlignment)
    applyButtonStateStyle(buttonStyle.defaultStyle, UIControlStateNormal)
    applyButtonStateStyle(buttonStyle.pressedStyle, UIControlStateHighlighted)
    applyButtonStateStyle(buttonStyle.disabledStyle, UIControlStateDisabled)
}

private fun UIButton.applyButtonStateStyle(style: ButtonStateStyle, state: UIControlState) {
    setTitleColor(style.textColor.uiColor, state)
    val bounds = bounds
    UIGraphicsBeginImageContext(CGSizeMake(bounds.useContents { size.width }, bounds.useContents { size.height }))
    CALayer().apply {
        frame = bounds
        applyBackgroundStyle(style.backgroundStyle, bounds)
        renderInContext(UIGraphicsGetCurrentContext())
    }
    setBackgroundImage(UIGraphicsGetImageFromCurrentImageContext(), state)
    UIGraphicsEndImageContext()
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.uikit

import com.splendo.kaluga.resources.stylable.TextAlignment
import platform.UIKit.NSTextAlignment
import platform.UIKit.NSTextAlignmentCenter
import platform.UIKit.NSTextAlignmentLeft
import platform.UIKit.NSTextAlignmentNatural
import platform.UIKit.NSTextAlignmentRight
import platform.UIKit.UIApplication
import platform.UIKit.UIControlContentHorizontalAlignment
import platform.UIKit.UIControlContentHorizontalAlignmentCenter
import platform.UIKit.UIControlContentHorizontalAlignmentLeading
import platform.UIKit.UIControlContentHorizontalAlignmentLeft
import platform.UIKit.UIControlContentHorizontalAlignmentRight
import platform.UIKit.UIControlContentHorizontalAlignmentTrailing
import platform.UIKit.UIUserInterfaceLayoutDirection

val TextAlignment.nsTextAlignment: NSTextAlignment get() = when (this) {
    TextAlignment.LEFT -> NSTextAlignmentLeft
    TextAlignment.RIGHT -> NSTextAlignmentRight
    TextAlignment.CENTER -> NSTextAlignmentCenter
    TextAlignment.END -> if (UIApplication.sharedApplication.userInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.UIUserInterfaceLayoutDirectionLeftToRight) {
        NSTextAlignmentRight
    } else {
        NSTextAlignmentLeft
    }
    TextAlignment.START -> NSTextAlignmentNatural
}

val TextAlignment.contentHorizontalAlignment: UIControlContentHorizontalAlignment get() = when (this) {
    TextAlignment.LEFT -> UIControlContentHorizontalAlignmentLeft
    TextAlignment.RIGHT -> UIControlContentHorizontalAlignmentRight
    TextAlignment.CENTER -> UIControlContentHorizontalAlignmentCenter
    TextAlignment.END -> UIControlContentHorizontalAlignmentTrailing
    TextAlignment.START -> UIControlContentHorizontalAlignmentLeading
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.resources.uikit

import com.splendo.kaluga.resources.stylable.TextStyle
import com.splendo.kaluga.resources.urlRanges
import com.splendo.kaluga.resources.view.KalugaLabel
import kotlinx.cinterop.CValue
import kotlinx.cinterop.ObjCAction
import kotlinx.cinterop.convert
import kotlinx.cinterop.readValue
import kotlinx.cinterop.useContents
import platform.CoreGraphics.CGFloat
import platform.CoreGraphics.CGPointMake
import platform.CoreGraphics.CGSizeMake
import platform.Foundation.NSLocationInRange
import platform.Foundation.NSMakeRange
import platform.Foundation.NSMapTable
import platform.Foundation.NSPointerFunctionsStrongMemory
import platform.Foundation.NSPointerFunctionsWeakMemory
import platform.Foundation.NSRange
import platform.Foundation.NSURL
import platform.Foundation.create
import platform.UIKit.NSLayoutManager
import platform.UIKit.NSTextAlignmentCenter
import platform.UIKit.NSTextAlignmentRight
import platform.UIKit.NSTextContainer
import platform.UIKit.NSTextStorage
import platform.UIKit.UIApplication
import platform.UIKit.UILabel
import platform.UIKit.UITapGestureRecognizer
import platform.UIKit.UITextField
import platform.UIKit.UITextItemInteraction
import platform.UIKit.UITextView
import platform.UIKit.UITextViewDelegateProtocol
import platform.UIKit.addGestureRecognizer
import platform.darwin.NSObject
import platform.darwin.sel_registerName
import kotlin.math.ceil

class UILinkTapGesture(private val label: UILabel) : NSObject() {

    @ThreadLocal
    object Registry {
        val registeredGestures = NSMapTable(NSPointerFunctionsWeakMemory, NSPointerFunctionsStrongMemory, 0)
    }

    @ObjCAction
    fun tapLabel(gesture: UITapGestureRecognizer) {
        label.attributedText?.urlRanges?.forEach { (range, url) ->
            if (didTapAttributedTextInLabel(gesture, range)) {
                UIApplication.sharedApplication.openURL(url)
            }
        }
    }

    private fun didTapAttributedTextInLabel(gesture: UITapGestureRecognizer, targetRange: NSRange): Boolean {
        // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
        val labelSize = label.bounds.useContents {
            CGSizeMake(size.width, size.height)
        }
        val textContainer = NSTextContainer(labelSize).apply {
            // Configure textContainer
            lineFragmentPadding = 0.0 as CGFloat
            lineBreakMode = label.lineBreakMode.convert()
            maximumNumberOfLines = label.numberOfLines.convert()
        }
        val layoutManager = NSLayoutManager().apply {
            addTextContainer(textContainer)
        }

        val attributedText = label.attributedText ?: return false
        NSTextStorage.create(attributedText).apply {
            addLayoutManager(layoutManager)
        }

        val alignmentOffset = when (label.textAlignment) {
            NSTextAlignmentCenter -> 0.5
            NSTextAlignmentRight -> 1.0
            else -> 0.0
        }

        // Find the tapped character location and compare it to the specified range
        val locationOfTouchInLabel = gesture.locationInView(label)
        val textBoundingBox = layoutManager.usedRectForTextContainer(textContainer)
        val textContainerOffset = CGPointMake(
            (((labelSize.useContents { width } - textBoundingBox.useContents { size.width }) * alignmentOffset) - textBoundingBox.useContents { origin.x }) as CGFloat,
            (((labelSize.useContents { height } - textBoundingBox.useContents { size.height }) * alignmentOffset) - textBoundingBox.useContents { origin.y }) as CGFloat
        ).useContents { this }
        val locationOfTouchInTextContainer = CGPointMake(
            locationOfTouchInLabel.useContents { x } - textContainerOffset.x,
            locationOfTouchInLabel.useContents { y } - textContainerOffset.y
        )
        val indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer, textContainer, null)

        val lineTapped = ceil(locationOfTouchInLabel.useContents { y } / label.font.lineHeight) - 1
        val rightMostPointInLineTapped = CGPointMake(labelSize.useContents { width }, label.font.lineHeight * lineTapped)
        val charsInLineTapped = layoutManager.characterIndexForPoint(rightMostPointInLineTapped, textContainer, null)
        if (indexOfCharacter >= charsInLineTapped) {
            return false
        }

        return NSLocationInRange(indexOfCharacter, targetRange.readValue())
    }
}

class UILinkTextViewDelegate : NSObject(), UITextViewDelegateProtocol {

    @ThreadLocal
    object Registry {
        val registeredDelegates = NSMapTable(NSPointerFunctionsWeakMemory, NSPointerFunctionsStrongMemory, 0)
    }

    override fun textView(
        textView: UITextView,
        shouldInteractWithURL: NSURL,
        inRange: CValue<NSRange>,
        interaction: UITextItemInteraction
    ): Boolean {
        return textView.attributedText.urlRanges.let { urls ->
            urls.firstOrNull { it.first.readValue().range == inRange.range && it.second == shouldInteractWithURL } != null
        }
    }
}

class UILinkTextViewDelegateWrapper(private val wrapped: UITextViewDelegateProtocol) : NSObject(), UITextViewDelegateProtocol by wrapped {

    private val linkDelegate = UILinkTextViewDelegate()

    override fun textView(
        textView: UITextView,
        shouldChangeTextInRange: CValue<NSRange>,
        replacementText: String
    ): Boolean {
        return linkDelegate.textView(textView, shouldChangeTextInRange, replacementText) || wrapped.textView(textView, shouldChangeTextInRange, replacementText)
    }
}

fun UILabel.bindLabel(label: KalugaLabel) {
    applyTextStyle(label.style)
    when (label) {
        is KalugaLabel.Plain -> text = label.text
        is KalugaLabel.Styled -> {
            attributedText = label.text.attributeString
            if (label.text.attributeString.urlRanges.isNotEmpty()) {
                val wrappedGesture = UILinkTapGesture(this)
                UILinkTapGesture.Registry.registeredGestures.setObject(wrappedGesture, this)
                val selector = sel_registerName("tapLabel:")
                addGestureRecognizer(UITapGestureRecognizer(wrappedGesture, selector))
                userInteractionEnabled = true
            } else {
                UILinkTapGesture.Registry.registeredGestures.removeObjectForKey(this)
            }
        }
    }
}

fun UITextView.bindTextView(label: KalugaLabel) {
    applyTextStyle(label.style)
    when (label) {
        is KalugaLabel.Plain -> text = label.text
        is KalugaLabel.Styled -> {
            attributedText = label.text.attributeString
            if (label.text.attributeString.urlRanges.isNotEmpty()) {
                val delegate = this.delegate()?.let { UILinkTextViewDelegateWrapper(it) } ?: UILinkTextViewDelegate()
                UILinkTextViewDelegate.Registry.registeredDelegates.setObject(delegate, this)
                setDelegate(delegate)
            } else {
                UILinkTextViewDelegate.Registry.registeredDelegates.removeObjectForKey(this)
            }
        }
    }
}

fun UILabel.applyTextStyle(textStyle: TextStyle) {
    setFont(textStyle.font.fontWithSize(textStyle.size.toDouble() as CGFloat))
    textColor = textStyle.color.uiColor
    textAlignment = textStyle.alignment.nsTextAlignment
    numberOfLines = 0
}

fun UITextView.applyTextStyle(textStyle: TextStyle) {
    setFont(textStyle.font.fontWithSize(textStyle.size.toDouble() as CGFloat))
    textColor = textStyle.color.uiColor
    textAlignment = textStyle.alignment.nsTextAlignment
}

fun UITextField.applyTextStyle(textStyle: TextStyle) {
    setFont(textStyle.font.fontWithSize(textStyle.size.toDouble() as CGFloat))
    textColor = textStyle.color.uiColor
    textAlignment = textStyle.alignment.nsTextAlignment
}

private val IntRange.nsRange: CValue<NSRange> get() = NSMakeRange(start.convert(), (endInclusive + 1 - start).convert())
private val CValue<NSRange>.range: IntRange get() = useContents { IntRange(location.toInt(), (location + length).toInt() - 1) }

/*
 Copyright 2022 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.formatter

import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.unit.ScientificUnit
import kotlin.math.roundToLong

private typealias CustomFormatHandler = (Number) -> String

sealed class CommonScientificValueFormatter(builder: Builder) : ScientificValueFormatter {
    class Builder {
        companion object {
            fun build(build: Builder.() -> Unit = {}): ScientificValueFormatter {
                val builder = Builder()
                build(builder)
                return Buildable(builder)
            }
        }

        internal val customFormatters = mutableMapOf<ScientificUnit<*>, CustomFormatHandler>()
        fun useFormat(unit: ScientificUnit<*>, format: CustomFormatHandler) {
            customFormatters[unit] = format
        }
    }

    private val customFormatters = builder.customFormatters

    override fun format(value: ScientificValue<*, *>): String {
        val customFormatter = customFormatters[value.unit]
        return customFormatter?.let { it(value.value.pretty()) } ?: defaultFormat(value)
    }

    private fun defaultFormat(value: ScientificValue<*, *>): String =
        "${value.value.pretty()} ${value.unit.symbol.withoutSpaces()}"

    /**
     *   The custom formatter with customisation applied using builder
     *   Is private as formatter of this type can be only created using builder
     */
    private class Buildable(builder: Builder) : CommonScientificValueFormatter(builder)

    /**
     *   Default formatter with no customisation applied
     */
    companion object Default : CommonScientificValueFormatter(Builder())
}

private fun Number.pretty() = toDouble().let {
    if (it.compareTo(it.roundToLong()) == 0) it.toLong() else this
}

private fun String.withoutSpaces() = filterNot(Char::isWhitespace)

/*
 Copyright 2022 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.formatter

import com.splendo.kaluga.scientific.ScientificValue

interface ScientificValueFormatter {
    fun format(value: ScientificValue<*, *>): String
}

fun ScientificValue<*, *>.toString(formatter: ScientificValueFormatter = CommonScientificValueFormatter): String =
    formatter.format(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.convert
import com.splendo.kaluga.scientific.invoke
import kotlinx.serialization.Serializable
import kotlin.native.concurrent.ThreadLocal

val MetricAccelerationUnits: Set<MetricAcceleration> get() = MetricSpeedUnits.flatMap { speed ->
    TimeUnits.map { speed per it }
}.toSet()

val ImperialAccelerationUnits: Set<ImperialAcceleration> get() = ImperialSpeedUnits.flatMap { speed ->
    TimeUnits.map { speed per it }
}.toSet()

val AccelerationUnits: Set<Acceleration> get() = MetricAccelerationUnits +
    ImperialAccelerationUnits

@Serializable
sealed class Acceleration : AbstractScientificUnit<PhysicalQuantity.Acceleration>() {
    abstract val speed: Speed
    abstract val per: Time
    override val quantity = PhysicalQuantity.Acceleration
    override val symbol: String by lazy {
        if (speed.per == per) {
            "${speed.distance.symbol} / ${per.symbol}2"
        } else {
            "${speed.distance.symbol} / (${speed.per.symbol} * ${per.symbol})"
        }
    }
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(speed.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = speed.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricAcceleration(
    override val speed: MetricSpeed,
    override val per: Time
) : Acceleration(), MetricScientificUnit<PhysicalQuantity.Acceleration> {
    override val system = MeasurementSystem.Metric
}

@Serializable
data class ImperialAcceleration(
    override val speed: ImperialSpeed,
    override val per: Time
) : Acceleration(), ImperialScientificUnit<PhysicalQuantity.Acceleration> {
    override val system = MeasurementSystem.Imperial
}

infix fun MetricSpeed.per(time: Time) = MetricAcceleration(this, time)
infix fun ImperialSpeed.per(time: Time) = ImperialAcceleration(this, time)

@ThreadLocal
val MetricStandardGravityAcceleration = 9.80665(Meter per Second per Second)
@ThreadLocal
val ImperialStandardGravityAcceleration = MetricStandardGravityAcceleration.convert(Foot per Second per Second)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricAndImperialActionUnits: Set<MetricAndImperialAction> get() = MetricAndImperialEnergyUnits.flatMap { energy ->
    TimeUnits.map { energy x it }
}.toSet()

val MetricActionUnits: Set<MetricAction> get() = MetricEnergyUnits.flatMap { energy ->
    TimeUnits.map { energy x it }
}.toSet()

val ImperialActionUnits: Set<ImperialAction> get() = ImperialEnergyUnits.flatMap { energy ->
    TimeUnits.map { energy x it }
}.toSet()

val ActionUnits: Set<Action> get() = MetricAndImperialActionUnits +
    MetricActionUnits.filter { it.energy !is MetricMetricAndImperialEnergyWrapper }.toSet() +
    ImperialActionUnits.filter { it.energy !is ImperialMetricAndImperialEnergyWrapper }.toSet()

@Serializable
sealed class Action : AbstractScientificUnit<PhysicalQuantity.Action>() {
    abstract val energy: Energy
    abstract val time: Time
    override val quantity = PhysicalQuantity.Action
    override val symbol: String by lazy { "${energy.symbol}⋅${time.symbol}" }
    override fun fromSIUnit(value: Decimal): Decimal = time.fromSIUnit(energy.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = energy.toSIUnit(time.toSIUnit(value))
}

@Serializable
data class MetricAndImperialAction(override val energy: MetricAndImperialEnergy, override val time: Time) : Action(), MetricAndImperialScientificUnit<PhysicalQuantity.Action> {
    override val system = MeasurementSystem.MetricAndImperial
    val metric get() = energy.metric x time
    val imperial get() = energy.imperial x time
}
@Serializable
data class MetricAction(override val energy: MetricEnergy, override val time: Time) : Action(), MetricScientificUnit<PhysicalQuantity.Action> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialAction(override val energy: ImperialEnergy, override val time: Time) : Action(), ImperialScientificUnit<PhysicalQuantity.Action> {
    override val system = MeasurementSystem.Imperial
}

infix fun MetricAndImperialEnergy.x(time: Time) = MetricAndImperialAction(this, time)
infix fun MetricEnergy.x(time: Time) = MetricAction(this, time)
infix fun ImperialEnergy.x(time: Time) = ImperialAction(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable
import kotlin.native.concurrent.ThreadLocal

val AmountOfSubstanceUnits: Set<AmountOfSubstance> get() = setOf(
    Mole,
    Nanomole,
    Micromole,
    Millimole,
    Centimole,
    Decimole,
    Decamole,
    Hectomole,
    Kilomole,
    Megamole,
    Gigamole
)

@Serializable
sealed class AmountOfSubstance : AbstractScientificUnit<PhysicalQuantity.AmountOfSubstance>(), MetricAndImperialScientificUnit<PhysicalQuantity.AmountOfSubstance>

@Serializable
object Mole : AmountOfSubstance(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.AmountOfSubstance> {
    override val symbol = "mol"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.AmountOfSubstance
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanomole : AmountOfSubstance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.AmountOfSubstance, Mole> by Nano(Mole)
@Serializable
object Micromole : AmountOfSubstance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.AmountOfSubstance, Mole> by Micro(Mole)
@Serializable
object Millimole : AmountOfSubstance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.AmountOfSubstance, Mole> by Milli(Mole)
@Serializable
object Centimole : AmountOfSubstance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.AmountOfSubstance, Mole> by Centi(Mole)
@Serializable
object Decimole : AmountOfSubstance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.AmountOfSubstance, Mole> by Deci(Mole)
@Serializable
object Decamole : AmountOfSubstance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.AmountOfSubstance, Mole> by Deca(Mole)
@Serializable
object Hectomole : AmountOfSubstance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.AmountOfSubstance, Mole> by Hecto(Mole)
@Serializable
object Kilomole : AmountOfSubstance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.AmountOfSubstance, Mole> by Kilo(Mole)
@Serializable
object Megamole : AmountOfSubstance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.AmountOfSubstance, Mole> by Mega(Mole)
@Serializable
object Gigamole : AmountOfSubstance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.AmountOfSubstance, Mole> by Giga(Mole)

@ThreadLocal
val AvogadroConstant = 6.02214076e23.toDecimal()

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable
import kotlin.math.PI

val AngleUnits: Set<Angle> get() = setOf(
    Radian,
    Nanoradian,
    Microradian,
    Milliradian,
    Centiradian,
    Deciradian,
    Turn,
    Nanoturn,
    Microturn,
    Milliturn,
    Centiturn,
    Deciturn,
    Degree,
    Gradian,
    ArcMinute,
    ArcSecond
)

@Serializable
sealed class Angle : AbstractScientificUnit<PhysicalQuantity.Angle>(), MetricAndImperialScientificUnit<PhysicalQuantity.Angle>

@Serializable
object Radian : Angle(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Angle> {
    override val symbol = "rad"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Angle
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanoradian : Angle(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Angle, Radian> by Nano(Radian)
@Serializable
object Microradian : Angle(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Angle, Radian> by Micro(Radian)
@Serializable
object Milliradian : Angle(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Angle, Radian> by Milli(Radian)
@Serializable
object Centiradian : Angle(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Angle, Radian> by Centi(Radian)
@Serializable
object Deciradian : Angle(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Angle, Radian> by Deci(Radian)

@Serializable
object Turn : Angle(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Angle> {
    private const val RADIAN_IN_TURN = 2.0 * PI
    override val symbol = "tr"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Angle
    override fun fromSIUnit(value: Decimal): Decimal = value / RADIAN_IN_TURN.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value * RADIAN_IN_TURN.toDecimal()
}

@Serializable
object Nanoturn : Angle(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Angle, Turn> by Nano(Turn)
@Serializable
object Microturn : Angle(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Angle, Turn> by Micro(Turn)
@Serializable
object Milliturn : Angle(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Angle, Turn> by Milli(Turn)
@Serializable
object Centiturn : Angle(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Angle, Turn> by Centi(Turn)
@Serializable
object Deciturn : Angle(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Angle, Turn> by Deci(Turn)

@Serializable
object Degree : Angle() {
    private const val DEGREE_IN_TURN = 360.0
    override val symbol = "°"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Angle
    override fun fromSIUnit(value: Decimal): Decimal = Turn.fromSIUnit(value) * DEGREE_IN_TURN.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = Turn.toSIUnit(value / DEGREE_IN_TURN.toDecimal())
}

@Serializable
object Gradian : Angle() {
    private const val GRADIAN_IN_TURN = 400.0
    override val symbol = "gon"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Angle
    override fun fromSIUnit(value: Decimal): Decimal = Turn.fromSIUnit(value) * GRADIAN_IN_TURN.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = Turn.toSIUnit(value / GRADIAN_IN_TURN.toDecimal())
}

@Serializable
object ArcMinute : Angle() {
    private const val ARCMINUTE_IN_TURN = 21600.0
    override val symbol = "′"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Angle
    override fun fromSIUnit(value: Decimal): Decimal = Turn.fromSIUnit(value) * ARCMINUTE_IN_TURN.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = Turn.toSIUnit(value / ARCMINUTE_IN_TURN.toDecimal())
}

@Serializable
object ArcSecond : Angle() {
    private const val ARCSECOND_IN_TURN = 1296000.0
    override val symbol = "″"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Angle
    override fun fromSIUnit(value: Decimal): Decimal = Turn.fromSIUnit(value) * ARCSECOND_IN_TURN.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = Turn.toSIUnit(value / ARCSECOND_IN_TURN.toDecimal())
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val AngularAccelerationUnits: Set<AngularAcceleration> get() = AngularVelocityUnits.flatMap { angularVelocity ->
    TimeUnits.map { angularVelocity per it }
}.toSet()

@Serializable
data class AngularAcceleration(val angularVelocity: AngularVelocity, val per: Time) : AbstractScientificUnit<PhysicalQuantity.AngularAcceleration>(), MetricAndImperialScientificUnit<PhysicalQuantity.AngularAcceleration> {
    override val quantity = PhysicalQuantity.AngularAcceleration
    override val system = MeasurementSystem.MetricAndImperial
    override val symbol: String by lazy {
        if (angularVelocity.per == per) {
            "${angularVelocity.angle.symbol} / ${per.symbol}2"
        } else {
            "${angularVelocity.angle.symbol} / (${angularVelocity.per.symbol} * ${per.symbol})"
        }
    }
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(angularVelocity.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = angularVelocity.toSIUnit(per.fromSIUnit(value))
}

infix fun AngularVelocity.per(time: Time) = AngularAcceleration(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val AngularVelocityUnits: Set<AngularVelocity> get() = AngleUnits.flatMap { angle ->
    TimeUnits.map { angle per it }
}.toSet()

@Serializable
data class AngularVelocity(val angle: Angle, val per: Time) : AbstractScientificUnit<PhysicalQuantity.AngularVelocity>(), MetricAndImperialScientificUnit<PhysicalQuantity.AngularVelocity> {
    override val quantity = PhysicalQuantity.AngularVelocity
    override val system = MeasurementSystem.MetricAndImperial
    override val symbol: String by lazy { "${angle.symbol} / ${per.symbol}" }
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(angle.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = angle.toSIUnit(per.fromSIUnit(value))
}

infix fun Angle.per(time: Time) = AngularVelocity(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricAreaDensityUnits: Set<MetricAreaDensity> get() = MetricWeightUnits.flatMap { weight ->
    MetricAreaUnits.map { weight per it }
}.toSet()

val ImperialAreaDensityUnits: Set<ImperialAreaDensity> get() = ImperialWeightUnits.flatMap { weight ->
    ImperialAreaUnits.map { weight per it }
}.toSet()

val UKImperialAreaDensityUnits: Set<UKImperialAreaDensity> get() = UKImperialWeightUnits.flatMap { weight ->
    ImperialAreaUnits.map { weight per it }
}.toSet()

val USCustomaryAreaDensityUnits: Set<USCustomaryAreaDensity> get() = USCustomaryWeightUnits.flatMap { weight ->
    ImperialAreaUnits.map { weight per it }
}.toSet()

val AreaDensityUnits: Set<AreaDensity> get() = MetricAreaDensityUnits +
    ImperialAreaDensityUnits +
    UKImperialAreaDensityUnits.filter { it.weight !is UKImperialImperialWeightWrapper }.toSet() +
    USCustomaryAreaDensityUnits.filter { it.weight !is USCustomaryImperialWeightWrapper }.toSet()

@Serializable
sealed class AreaDensity : AbstractScientificUnit<PhysicalQuantity.AreaDensity>() {
    abstract val weight: Weight
    abstract val per: Area
    override val symbol: String by lazy { "${weight.symbol} / ${per.symbol}" }
    override val quantity = PhysicalQuantity.AreaDensity
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(weight.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = weight.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricAreaDensity(override val weight: MetricWeight, override val per: MetricArea) : AreaDensity(), MetricScientificUnit<PhysicalQuantity.AreaDensity> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialAreaDensity(override val weight: ImperialWeight, override val per: ImperialArea) : AreaDensity(), ImperialScientificUnit<PhysicalQuantity.AreaDensity> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = weight.ukImperial per per
    val usCustomary get() = weight.usCustomary per per
}
@Serializable
data class USCustomaryAreaDensity(override val weight: USCustomaryWeight, override val per: ImperialArea) : AreaDensity(), USCustomaryScientificUnit<PhysicalQuantity.AreaDensity> {
    override val system = MeasurementSystem.USCustomary
}
@Serializable
data class UKImperialAreaDensity(override val weight: UKImperialWeight, override val per: ImperialArea) : AreaDensity(), UKImperialScientificUnit<PhysicalQuantity.AreaDensity> {
    override val system = MeasurementSystem.UKImperial
}

infix fun MetricWeight.per(area: MetricArea) = MetricAreaDensity(this, area)
infix fun ImperialWeight.per(area: ImperialArea) = ImperialAreaDensity(this, area)
infix fun USCustomaryWeight.per(area: ImperialArea) = USCustomaryAreaDensity(this, area)
infix fun UKImperialWeight.per(area: ImperialArea) = UKImperialAreaDensity(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricAreaUnits: Set<MetricArea> get() = setOf(
    SquareMeter,
    SquareNanometer,
    SquareMicrometer,
    SquareMillimeter,
    SquareCentimeter,
    SquareDecimeter,
    SquareDecameter,
    SquareHectometer,
    SquareKilometer,
    Hectare
)

val ImperialAreaUnits: Set<ImperialArea> get() = setOf(
    SquareInch,
    SquareFoot,
    SquareYard,
    SquareMile,
    Acre
)

val AreaUnits: Set<Area> get() = MetricAreaUnits + ImperialAreaUnits

@Serializable
sealed class Area : AbstractScientificUnit<PhysicalQuantity.Area>()

@Serializable
sealed class MetricArea : Area(), MetricScientificUnit<PhysicalQuantity.Area>

@Serializable
sealed class ImperialArea : Area(), ImperialScientificUnit<PhysicalQuantity.Area>

class Square<S : MeasurementSystem, U : SystemScientificUnit<S, PhysicalQuantity.Length>>(private val unit: U) : SystemScientificUnit<S, PhysicalQuantity.Area> {
    override val symbol: String = "${unit.symbol}2"
    override val system: S = unit.system
    override val quantity = PhysicalQuantity.Area
    override fun fromSIUnit(value: Decimal): Decimal = unit.fromSIUnit(unit.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = unit.toSIUnit(unit.toSIUnit(value))
}

// Metric Volume
@Serializable
object SquareMeter : MetricArea(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Area> by Square(Meter)

@Serializable
object SquareDecimeter : MetricArea(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Area> by Square(Deci(Meter))

@Serializable
object SquareCentimeter : MetricArea(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Area> by Square(Centi(Meter))

@Serializable
object SquareMillimeter : MetricArea(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Area> by Square(Milli(Meter))

@Serializable
object SquareMicrometer : MetricArea(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Area> by Square(Micro(Meter))

@Serializable
object SquareNanometer : MetricArea(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Area> by Square(Nano(Meter))

@Serializable
object SquareDecameter : MetricArea(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Area> by Square(Deca(Meter))

@Serializable
object SquareHectometer : MetricArea(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Area> by Square(Hecto(Meter))

@Serializable
object Hectare : MetricArea(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Area> by Square(Hecto(Meter)) {
    override val symbol: String = "ha"
}

@Serializable
object SquareKilometer : MetricArea(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Area> by Square(Kilo(Meter))

@Serializable
object SquareMegameter : MetricArea(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Area> by Square(Mega(Meter))

@Serializable
object SquareGigameter : MetricArea(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Area> by Square(Giga(Meter))

@Serializable
object SquareMile : ImperialArea(), SystemScientificUnit<MeasurementSystem.Imperial, PhysicalQuantity.Area> by Square(Mile) {
    override val symbol: String = "sq. mi"
}

@Serializable
object SquareYard : ImperialArea(), SystemScientificUnit<MeasurementSystem.Imperial, PhysicalQuantity.Area> by Square(Yard) {
    override val symbol: String = "sq. yd"
}

@Serializable
object SquareFoot : ImperialArea(), SystemScientificUnit<MeasurementSystem.Imperial, PhysicalQuantity.Area> by Square(Foot) {
    override val symbol: String = "sq. fr"
}

@Serializable
object SquareInch : ImperialArea(), SystemScientificUnit<MeasurementSystem.Imperial, PhysicalQuantity.Area> by Square(Inch) {
    override val symbol: String = "sq. in"
}

@Serializable
object Acre : ImperialArea() {
    override val symbol: String = "acre"
    val ACRES_IN_SQUARE_MILE = 640.0
    override val quantity = PhysicalQuantity.Area
    override val system = MeasurementSystem.Imperial
    override fun toSIUnit(value: Decimal): Decimal = SquareMile.toSIUnit(value / ACRES_IN_SQUARE_MILE.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = SquareMile.fromSIUnit(value) * ACRES_IN_SQUARE_MILE.toDecimal()
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val CatalysticActivityUnits: Set<CatalysticActivity> get() = setOf(
    Katal,
    Nanokatal,
    Microkatal,
    Millikatal,
    Centikatal,
    Decikatal,
    Decakatal,
    Hectokatal,
    Kilokatal,
    Megakatal,
    Gigakatal
)

@Serializable
sealed class CatalysticActivity : AbstractScientificUnit<PhysicalQuantity.CatalysticActivity>(), MetricAndImperialScientificUnit<PhysicalQuantity.CatalysticActivity>

@Serializable
object Katal : CatalysticActivity(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.CatalysticActivity> {
    override val symbol = "kat"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.CatalysticActivity
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanokatal : CatalysticActivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.CatalysticActivity, Katal> by Nano(Katal)
@Serializable
object Microkatal : CatalysticActivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.CatalysticActivity, Katal> by Micro(Katal)
@Serializable
object Millikatal : CatalysticActivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.CatalysticActivity, Katal> by Milli(Katal)
@Serializable
object Centikatal : CatalysticActivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.CatalysticActivity, Katal> by Centi(Katal)
@Serializable
object Decikatal : CatalysticActivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.CatalysticActivity, Katal> by Deci(Katal)
@Serializable
object Decakatal : CatalysticActivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.CatalysticActivity, Katal> by Deca(Katal)
@Serializable
object Hectokatal : CatalysticActivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.CatalysticActivity, Katal> by Hecto(Katal)
@Serializable
object Kilokatal : CatalysticActivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.CatalysticActivity, Katal> by Kilo(Katal)
@Serializable
object Megakatal : CatalysticActivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.CatalysticActivity, Katal> by Mega(Katal)
@Serializable
object Gigakatal : CatalysticActivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.CatalysticActivity, Katal> by Giga(Katal)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricDensityUnits: Set<MetricDensity> get() = MetricWeightUnits.flatMap { weight ->
    MetricVolumeUnits.map { weight per it }
}.toSet()

val ImperialDensityUnits: Set<ImperialDensity> get() = ImperialWeightUnits.flatMap { weight ->
    ImperialVolumeUnits.map { weight per it }
}.toSet()

val UKImperialDensityUnits: Set<UKImperialDensity> get() = UKImperialWeightUnits.flatMap { weight ->
    UKImperialVolumeUnits.map { weight per it }
}.toSet()

val USCustomaryDensityUnits: Set<USCustomaryDensity> get() = USCustomaryWeightUnits.flatMap { weight ->
    USCustomaryVolumeUnits.map { weight per it }
}.toSet()

val DensityUnits: Set<Density> get() = MetricDensityUnits +
    ImperialDensityUnits +
    UKImperialDensityUnits.filter { it.weight !is UKImperialImperialWeightWrapper || it.per !is UKImperialImperialVolumeWrapper }.toSet() +
    USCustomaryDensityUnits.filter { it.weight !is USCustomaryImperialWeightWrapper || it.per !is USCustomaryImperialVolumeWrapper }.toSet()

@Serializable
sealed class Density : AbstractScientificUnit<PhysicalQuantity.Density>() {
    abstract val weight: Weight
    abstract val per: Volume
    override val symbol: String by lazy { "${weight.symbol} / ${per.symbol}" }
    override val quantity = PhysicalQuantity.Density
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(weight.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = weight.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricDensity(override val weight: MetricWeight, override val per: MetricVolume) : Density(), MetricScientificUnit<PhysicalQuantity.Density> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialDensity(override val weight: ImperialWeight, override val per: ImperialVolume) : Density(), ImperialScientificUnit<PhysicalQuantity.Density> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = weight.ukImperial per per.ukImperial
    val usCustomary get() = weight.usCustomary per per.usCustomary
}
@Serializable
data class USCustomaryDensity(override val weight: USCustomaryWeight, override val per: USCustomaryVolume) : Density(), USCustomaryScientificUnit<PhysicalQuantity.Density> {
    override val system = MeasurementSystem.USCustomary
}
@Serializable
data class UKImperialDensity(override val weight: UKImperialWeight, override val per: UKImperialVolume) : Density(), UKImperialScientificUnit<PhysicalQuantity.Density> {
    override val system = MeasurementSystem.UKImperial
}

infix fun MetricWeight.per(volume: MetricVolume) = MetricDensity(this, volume)
infix fun ImperialWeight.per(volume: ImperialVolume) = ImperialDensity(this, volume)
infix fun ImperialWeight.per(volume: UKImperialVolume) = UKImperialDensity(this.ukImperial, volume)
infix fun ImperialWeight.per(volume: USCustomaryVolume) = USCustomaryDensity(this.usCustomary, volume)
infix fun USCustomaryWeight.per(volume: USCustomaryVolume) = USCustomaryDensity(this, volume)
infix fun USCustomaryWeight.per(volume: ImperialVolume) = USCustomaryDensity(this, volume.usCustomary)
infix fun UKImperialWeight.per(volume: ImperialVolume) = UKImperialDensity(this, volume.ukImperial)
infix fun UKImperialWeight.per(volume: UKImperialVolume) = UKImperialDensity(this, volume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricDynamicViscosityUnits: Set<MetricDynamicViscosity> get() = MetricPressureUnits.flatMap { pressure ->
    TimeUnits.map { pressure x it }
}.toSet()

val ImperialDynamicViscosityUnits: Set<ImperialDynamicViscosity> get() = ImperialPressureUnits.flatMap { pressure ->
    TimeUnits.map { pressure x it }
}.toSet()

val UKImperialDynamicViscosityUnits: Set<UKImperialDynamicViscosity> get() = UKImperialPressureUnits.flatMap { pressure ->
    TimeUnits.map { pressure x it }
}.toSet()

val USCustomaryDynamicViscosityUnits: Set<USCustomaryDynamicViscosity> get() = USCustomaryPressureUnits.flatMap { pressure ->
    TimeUnits.map { pressure x it }
}.toSet()

val DynamicViscosityUnits: Set<DynamicViscosity> get() = MetricDynamicViscosityUnits +
    ImperialDynamicViscosityUnits +
    UKImperialDynamicViscosityUnits.filter { it.pressure !is UKImperialPressureWrapper }.toSet() +
    USCustomaryDynamicViscosityUnits.filter { it.pressure !is USCustomaryImperialPressureWrapper }.toSet()

@Serializable
sealed class DynamicViscosity : AbstractScientificUnit<PhysicalQuantity.DynamicViscosity>() {
    abstract val pressure: Pressure
    abstract val time: Time
    override val quantity = PhysicalQuantity.DynamicViscosity
    override val symbol: String by lazy { "${pressure.symbol}⋅${time.symbol}" }
    override fun fromSIUnit(value: Decimal): Decimal = time.fromSIUnit(pressure.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = pressure.toSIUnit(time.toSIUnit(value))
}

@Serializable
data class MetricDynamicViscosity(override val pressure: MetricPressure, override val time: Time) : DynamicViscosity(), MetricScientificUnit<PhysicalQuantity.DynamicViscosity> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialDynamicViscosity(override val pressure: ImperialPressure, override val time: Time) : DynamicViscosity(), ImperialScientificUnit<PhysicalQuantity.DynamicViscosity> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = pressure.ukImperial x time
    val usCustomary get() = pressure.usCustomary x time
}
@Serializable
data class UKImperialDynamicViscosity(override val pressure: UKImperialPressure, override val time: Time) : DynamicViscosity(), UKImperialScientificUnit<PhysicalQuantity.DynamicViscosity> {
    override val system = MeasurementSystem.UKImperial
}
@Serializable
data class USCustomaryDynamicViscosity(override val pressure: USCustomaryPressure, override val time: Time) : DynamicViscosity(), USCustomaryScientificUnit<PhysicalQuantity.DynamicViscosity> {
    override val system = MeasurementSystem.USCustomary
}

infix fun MetricPressure.x(time: Time) = MetricDynamicViscosity(this, time)
infix fun ImperialPressure.x(time: Time) = ImperialDynamicViscosity(this, time)
infix fun UKImperialPressure.x(time: Time) = UKImperialDynamicViscosity(this, time)
infix fun USCustomaryPressure.x(time: Time) = USCustomaryDynamicViscosity(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val ElectricCapacitanceUnits: Set<ElectricCapacitance> get() = setOf(
    Farad,
    Nanofarad,
    Microfarad,
    Millifarad,
    Centifarad,
    Decifarad,
    Decafarad,
    Hectofarad,
    Kilofarad,
    Megafarad,
    Gigafarad
)

@Serializable
sealed class ElectricCapacitance : AbstractScientificUnit<PhysicalQuantity.ElectricCapacitance>(), MetricAndImperialScientificUnit<PhysicalQuantity.ElectricCapacitance>

@Serializable
object Farad : ElectricCapacitance(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCapacitance> {
    override val symbol = "F"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.ElectricCapacitance
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanofarad : ElectricCapacitance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCapacitance, Farad> by Nano(Farad)
@Serializable
object Microfarad : ElectricCapacitance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCapacitance, Farad> by Micro(Farad)
@Serializable
object Millifarad : ElectricCapacitance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCapacitance, Farad> by Milli(Farad)
@Serializable
object Centifarad : ElectricCapacitance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCapacitance, Farad> by Centi(Farad)
@Serializable
object Decifarad : ElectricCapacitance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCapacitance, Farad> by Deci(Farad)
@Serializable
object Decafarad : ElectricCapacitance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCapacitance, Farad> by Deca(Farad)
@Serializable
object Hectofarad : ElectricCapacitance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCapacitance, Farad> by Hecto(Farad)
@Serializable
object Kilofarad : ElectricCapacitance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCapacitance, Farad> by Kilo(Farad)
@Serializable
object Megafarad : ElectricCapacitance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCapacitance, Farad> by Mega(Farad)
@Serializable
object Gigafarad : ElectricCapacitance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCapacitance, Farad> by Giga(Farad)
@Serializable
object Abfarad : ElectricCapacitance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCapacitance, Farad> by Giga(Farad) {
    override val symbol: String = "abF"
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.invoke
import kotlinx.serialization.Serializable
import kotlin.native.concurrent.ThreadLocal

val ElectricChargeUnits: Set<ElectricCharge> get() = setOf(
    Coulomb,
    Nanocoulomb,
    Microcoulomb,
    Millicoulomb,
    Centicoulomb,
    Decicoulomb,
    Decacoulomb,
    Hectocoulomb,
    Kilocoulomb,
    Megacoulomb,
    Gigacoulomb,
    Abcoulomb
)

@Serializable
sealed class ElectricCharge : AbstractScientificUnit<PhysicalQuantity.ElectricCharge>(), MetricAndImperialScientificUnit<PhysicalQuantity.ElectricCharge>

@Serializable
object Coulomb : ElectricCharge(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCharge> {
    override val symbol = "C"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.ElectricCharge
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanocoulomb : ElectricCharge(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCharge, Coulomb> by Nano(Coulomb)
@Serializable
object Microcoulomb : ElectricCharge(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCharge, Coulomb> by Micro(Coulomb)
@Serializable
object Millicoulomb : ElectricCharge(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCharge, Coulomb> by Milli(Coulomb)
@Serializable
object Centicoulomb : ElectricCharge(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCharge, Coulomb> by Centi(Coulomb)
@Serializable
object Decicoulomb : ElectricCharge(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCharge, Coulomb> by Deci(Coulomb)
@Serializable
object Decacoulomb : ElectricCharge(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCharge, Coulomb> by Deca(Coulomb)
@Serializable
object Abcoulomb : ElectricCharge(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCharge, Coulomb> by Deca(Coulomb) {
    override val symbol: String = "abC"
}
@Serializable
object Hectocoulomb : ElectricCharge(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCharge, Coulomb> by Hecto(Coulomb)
@Serializable
object Kilocoulomb : ElectricCharge(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCharge, Coulomb> by Kilo(Coulomb)
@Serializable
object Megacoulomb : ElectricCharge(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCharge, Coulomb> by Mega(Coulomb)
@Serializable
object Gigacoulomb : ElectricCharge(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCharge, Coulomb> by Giga(Coulomb)

@ThreadLocal
val elementaryCharge = 1.602176634e-19(Coulomb)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val ElectricConductanceUnits: Set<ElectricConductance> get() = setOf(
    Siemens,
    Nanosiemens,
    Microsiemens,
    Millisiemens,
    Centisiemens,
    Decisiemens,
    Decasiemens,
    Hectosiemens,
    Kilosiemens,
    Megasiemens,
    Gigasiemens,
    Absiemens
)

@Serializable
sealed class ElectricConductance : AbstractScientificUnit<PhysicalQuantity.ElectricConductance>(), MetricAndImperialScientificUnit<PhysicalQuantity.ElectricConductance>

@Serializable
object Siemens : ElectricConductance(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricConductance> {
    override val symbol = "S"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.ElectricConductance
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanosiemens : ElectricConductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricConductance, Siemens> by Nano(Siemens)
@Serializable
object Microsiemens : ElectricConductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricConductance, Siemens> by Micro(Siemens)
@Serializable
object Millisiemens : ElectricConductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricConductance, Siemens> by Milli(Siemens)
@Serializable
object Centisiemens : ElectricConductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricConductance, Siemens> by Centi(Siemens)
@Serializable
object Decisiemens : ElectricConductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricConductance, Siemens> by Deci(Siemens)
@Serializable
object Decasiemens : ElectricConductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricConductance, Siemens> by Deca(Siemens)
@Serializable
object Hectosiemens : ElectricConductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricConductance, Siemens> by Hecto(Siemens)
@Serializable
object Kilosiemens : ElectricConductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricConductance, Siemens> by Kilo(Siemens)
@Serializable
object Megasiemens : ElectricConductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricConductance, Siemens> by Mega(Siemens)
@Serializable
object Gigasiemens : ElectricConductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricConductance, Siemens> by Giga(Siemens)
@Serializable
object Absiemens : ElectricConductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricConductance, Siemens> by Giga(Siemens) {
    override val symbol: String = "abS"
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val ElectricCurrentUnits: Set<ElectricCurrent> get() = setOf(
    Ampere,
    Nanoampere,
    Microampere,
    Milliampere,
    Centiampere,
    Deciampere,
    Decaampere,
    Hectoampere,
    Kiloampere,
    Megaampere,
    Gigaampere,
    Biot,
    Abampere
)

@Serializable
sealed class ElectricCurrent : AbstractScientificUnit<PhysicalQuantity.ElectricCurrent>(), MetricAndImperialScientificUnit<PhysicalQuantity.ElectricCurrent>

@Serializable
object Ampere : ElectricCurrent(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCurrent> {
    override val symbol = "A"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.ElectricCurrent
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanoampere : ElectricCurrent(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCurrent, Ampere> by Nano(Ampere)
@Serializable
object Microampere : ElectricCurrent(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCurrent, Ampere> by Micro(Ampere)
@Serializable
object Milliampere : ElectricCurrent(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCurrent, Ampere> by Milli(Ampere)
@Serializable
object Centiampere : ElectricCurrent(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCurrent, Ampere> by Centi(Ampere)
@Serializable
object Deciampere : ElectricCurrent(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCurrent, Ampere> by Deci(Ampere)
@Serializable
object Decaampere : ElectricCurrent(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCurrent, Ampere> by Deca(Ampere)
@Serializable
object Abampere : ElectricCurrent(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCurrent, Ampere> by Deca(Ampere) {
    override val symbol: String = "abA"
}
@Serializable
object Biot : ElectricCurrent(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCurrent, Ampere> by Deca(Ampere) {
    override val symbol: String = "B"
}
@Serializable
object Hectoampere : ElectricCurrent(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCurrent, Ampere> by Hecto(Ampere)
@Serializable
object Kiloampere : ElectricCurrent(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCurrent, Ampere> by Kilo(Ampere)
@Serializable
object Megaampere : ElectricCurrent(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCurrent, Ampere> by Mega(Ampere)
@Serializable
object Gigaampere : ElectricCurrent(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricCurrent, Ampere> by Giga(Ampere)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val ElectricInductanceUnits: Set<ElectricInductance> get() = setOf(
    Henry,
    Nanohenry,
    Microhenry,
    Millihenry,
    Centihenry,
    Decihenry,
    Decahenry,
    Hectohenry,
    Kilohenry,
    Megahenry,
    Gigahenry,
    Abhenry
)

@Serializable
sealed class ElectricInductance : AbstractScientificUnit<PhysicalQuantity.ElectricInductance>(), MetricAndImperialScientificUnit<PhysicalQuantity.ElectricInductance>

@Serializable
object Henry : ElectricInductance(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricInductance> {
    override val symbol = "H"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.ElectricInductance
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanohenry : ElectricInductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricInductance, Henry> by Nano(Henry)
@Serializable
object Abhenry : ElectricInductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricInductance, Henry> by Nano(Henry) {
    override val symbol: String = "abH"
}
@Serializable
object Microhenry : ElectricInductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricInductance, Henry> by Micro(Henry)
@Serializable
object Millihenry : ElectricInductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricInductance, Henry> by Milli(Henry)
@Serializable
object Centihenry : ElectricInductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricInductance, Henry> by Centi(Henry)
@Serializable
object Decihenry : ElectricInductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricInductance, Henry> by Deci(Henry)
@Serializable
object Decahenry : ElectricInductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricInductance, Henry> by Deca(Henry)
@Serializable
object Hectohenry : ElectricInductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricInductance, Henry> by Hecto(Henry)
@Serializable
object Kilohenry : ElectricInductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricInductance, Henry> by Kilo(Henry)
@Serializable
object Megahenry : ElectricInductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricInductance, Henry> by Mega(Henry)
@Serializable
object Gigahenry : ElectricInductance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricInductance, Henry> by Giga(Henry)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val ElectricResistanceUnits: Set<ElectricResistance> get() = setOf(
    Ohm,
    Nanoohm,
    Microohm,
    Milliohm,
    Centiohm,
    Deciohm,
    Decaohm,
    HectoOhm,
    Kiloohm,
    Megaohm,
    Gigaohm,
    Abohm
)

@Serializable
sealed class ElectricResistance : AbstractScientificUnit<PhysicalQuantity.ElectricResistance>(), MetricAndImperialScientificUnit<PhysicalQuantity.ElectricResistance>

@Serializable
object Ohm : ElectricResistance(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricResistance> {
    override val symbol = "Ω"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.ElectricResistance
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanoohm : ElectricResistance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricResistance, Ohm> by Nano(Ohm)
@Serializable
object Abohm : ElectricResistance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricResistance, Ohm> by Nano(Ohm) {
    override val symbol = "abΩ"
}
@Serializable
object Microohm : ElectricResistance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricResistance, Ohm> by Micro(Ohm)
@Serializable
object Milliohm : ElectricResistance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricResistance, Ohm> by Milli(Ohm)
@Serializable
object Centiohm : ElectricResistance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricResistance, Ohm> by Centi(Ohm)
@Serializable
object Deciohm : ElectricResistance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricResistance, Ohm> by Deci(Ohm)
@Serializable
object Decaohm : ElectricResistance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricResistance, Ohm> by Deca(Ohm)
@Serializable
object HectoOhm : ElectricResistance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricResistance, Ohm> by Hecto(Ohm)
@Serializable
object Kiloohm : ElectricResistance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricResistance, Ohm> by Kilo(Ohm)
@Serializable
object Megaohm : ElectricResistance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricResistance, Ohm> by Mega(Ohm)
@Serializable
object Gigaohm : ElectricResistance(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.ElectricResistance, Ohm> by Giga(Ohm)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricEnergyUnits: Set<MetricEnergy> get() = setOf(
    Joule,
    Nanojoule,
    Microjoule,
    Millijoule,
    Centijoule,
    Decijoule,
    Decajoule,
    Hectojoule,
    Kilojoule,
    Megajoule,
    Gigajoule,
    Erg,
    Nanoerg,
    Microerg,
    Millierg,
    Centierg,
    Decierg,
    Decaerg,
    Hectoerg,
    Kiloerg,
    Megaerg,
    Gigaerg,
    Electronvolt,
    Nanoelectronvolt,
    Microelectronvolt,
    Millielectronvolt,
    Centielectronvolt,
    Decielectronvolt,
    Decaelectronvolt,
    Hectoelectronvolt,
    Kiloelectronvolt,
    Megaelectronvolt,
    Gigaelectronvolt
) + MetricAndImperialEnergyUnits.map { it.metric }.toSet()

val MetricAndImperialEnergyUnits: Set<MetricAndImperialEnergy> get() = setOf(
    WattHour,
    NanowattHour,
    MicrowattHour,
    MilliwattHour,
    CentiwattHour,
    DeciwattHour,
    DecawattHour,
    HectowattHour,
    KilowattHour,
    MegawattHour,
    GigawattHour,
    Calorie,
    Calorie.IT,
    Millicalorie,
    Millicalorie.IT,
    Kilocalorie,
    Kilocalorie.IT,
    Megacalorie,
    Megacalorie.IT
)

val ImperialEnergyUnits: Set<ImperialEnergy> get() = setOf(
    FootPoundal,
    FootPoundForce,
    InchPoundForce,
    InchOunceForce,
    HorsepowerHour,
    BritishThermalUnit,
    BritishThermalUnit.Thermal
) + MetricAndImperialEnergyUnits.map { it.imperial }.toSet()

val EnergyUnits: Set<Energy> get() = MetricAndImperialEnergyUnits + MetricEnergyUnits.filter { it !is MetricMetricAndImperialEnergyWrapper }.toSet() + ImperialEnergyUnits.filter { it !is ImperialMetricAndImperialEnergyWrapper }.toSet()

@Serializable
sealed class Energy : AbstractScientificUnit<PhysicalQuantity.Energy>()

@Serializable
sealed class MetricEnergy : Energy(), MetricScientificUnit<PhysicalQuantity.Energy>
@Serializable
sealed class ImperialEnergy : Energy(), ImperialScientificUnit<PhysicalQuantity.Energy>
@Serializable
sealed class MetricAndImperialEnergy : Energy(), MetricAndImperialScientificUnit<PhysicalQuantity.Energy>

@Serializable
object Joule : MetricEnergy(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy> {
    override val symbol: String = "J"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Energy
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}
@Serializable
object Nanojoule : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Joule> by Nano(Joule)
@Serializable
object Microjoule : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Joule> by Micro(Joule)
@Serializable
object Millijoule : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Joule> by Milli(Joule)
@Serializable
object Centijoule : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Joule> by Centi(Joule)
@Serializable
object Decijoule : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Joule> by Deci(Joule)
@Serializable
object Decajoule : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Joule> by Deca(Joule)
@Serializable
object Hectojoule : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Joule> by Hecto(Joule)
@Serializable
object Kilojoule : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Joule> by Kilo(Joule)
@Serializable
object Megajoule : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Joule> by Mega(Joule)
@Serializable
object Gigajoule : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Joule> by Giga(Joule)

@Serializable
object Erg : MetricEnergy(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy> {
    const val ERG_IN_JOULE = 10000000
    override val symbol: String = "erg"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Energy
    override fun fromSIUnit(value: Decimal): Decimal = value * ERG_IN_JOULE.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value / ERG_IN_JOULE.toDecimal()
}

@Serializable
object Nanoerg : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> by Nano(Erg)
@Serializable
object Microerg : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> by Micro(Erg)
@Serializable
object Millierg : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> by Milli(Erg)
@Serializable
object Centierg : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> by Centi(Erg)
@Serializable
object Decierg : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> by Deci(Erg)
@Serializable
object Decaerg : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> by Deca(Erg)
@Serializable
object Hectoerg : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> by Hecto(Erg)
@Serializable
object Kiloerg : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> by Kilo(Erg)
@Serializable
object Megaerg : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> by Mega(Erg)
@Serializable
object Gigaerg : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> by Giga(Erg)

@Serializable
object WattHour : MetricAndImperialEnergy(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy> {
    override val symbol: String = "Wh"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Energy
    override fun fromSIUnit(value: Decimal): Decimal = Hour.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = Hour.toSIUnit(value)
}
@Serializable
object NanowattHour : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, WattHour> by Nano(WattHour)
@Serializable
object MicrowattHour : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, WattHour> by Micro(WattHour)
@Serializable
object MilliwattHour : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, WattHour> by Milli(WattHour)
@Serializable
object CentiwattHour : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, WattHour> by Centi(WattHour)
@Serializable
object DeciwattHour : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, WattHour> by Deci(WattHour)
@Serializable
object DecawattHour : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, WattHour> by Deca(WattHour)
@Serializable
object HectowattHour : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, WattHour> by Hecto(WattHour)
@Serializable
object KilowattHour : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, WattHour> by Kilo(WattHour)
@Serializable
object MegawattHour : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, WattHour> by Mega(WattHour)
@Serializable
object GigawattHour : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, WattHour> by Giga(WattHour)

@Serializable
object Electronvolt : MetricEnergy(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy> {
    override val symbol: String = "eV"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Energy
    override fun fromSIUnit(value: Decimal): Decimal = value.div(elementaryCharge.decimalValue, 29)
    override fun toSIUnit(value: Decimal): Decimal = value.times(elementaryCharge.decimalValue, 29)
}

@Serializable
object Nanoelectronvolt : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Electronvolt> by Nano(Electronvolt)
@Serializable
object Microelectronvolt : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Electronvolt> by Micro(Electronvolt)
@Serializable
object Millielectronvolt : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Electronvolt> by Milli(Electronvolt)
@Serializable
object Centielectronvolt : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Electronvolt> by Centi(Electronvolt)
@Serializable
object Decielectronvolt : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Electronvolt> by Deci(Electronvolt)
@Serializable
object Decaelectronvolt : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Electronvolt> by Deca(Electronvolt)
@Serializable
object Hectoelectronvolt : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Electronvolt> by Hecto(Electronvolt)
@Serializable
object Kiloelectronvolt : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Electronvolt> by Kilo(Electronvolt)
@Serializable
object Megaelectronvolt : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Electronvolt> by Mega(Electronvolt)
@Serializable
object Gigaelectronvolt : MetricEnergy(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Electronvolt> by Giga(Electronvolt)

interface CalorieUnit : MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy>
@Serializable
object Calorie : MetricAndImperialEnergy(), CalorieUnit by CalorieBase(4.184.toDecimal()) {

    internal class CalorieBase(val jouleInCalorie: Decimal, symbolPostfix: String = "") : CalorieUnit {
        override val symbol: String = "Cal$symbolPostfix"
        override val system = MeasurementSystem.MetricAndImperial
        override val quantity = PhysicalQuantity.Energy
        override fun fromSIUnit(value: Decimal): Decimal = value / jouleInCalorie
        override fun toSIUnit(value: Decimal): Decimal = value * jouleInCalorie
    }

    @Serializable
    object IT : MetricAndImperialEnergy(), CalorieUnit by CalorieBase(4.1868.toDecimal(), "-IT")
}
@Serializable
object Millicalorie : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, Calorie> by Milli(Calorie) {
    @Serializable
    object IT : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, Calorie.IT> by Milli(Calorie.IT)
}
@Serializable
object Kilocalorie : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, Calorie> by Kilo(Calorie) {
    @Serializable
    object IT : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, Calorie.IT> by Kilo(Calorie.IT)
}
@Serializable
object Megacalorie : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, Calorie> by Mega(Calorie) {
    @Serializable
    object IT : MetricAndImperialEnergy(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, Calorie.IT> by Mega(Calorie.IT)
}

@Serializable
object FootPoundal : ImperialEnergy() {
    override val symbol: String = "ftpdl"
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Energy
    override fun fromSIUnit(value: Decimal): Decimal = Foot.fromSIUnit(Poundal.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = Poundal.toSIUnit(Foot.toSIUnit(value))
}
@Serializable
object FootPoundForce : ImperialEnergy() {
    override val symbol: String = "ftlbf"
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Energy
    override fun fromSIUnit(value: Decimal): Decimal = Foot.fromSIUnit(PoundForce.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = PoundForce.toSIUnit(Foot.toSIUnit(value))
}
@Serializable
object InchPoundForce : ImperialEnergy() {
    override val symbol: String = "inlbf"
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Energy
    override fun fromSIUnit(value: Decimal): Decimal = Inch.fromSIUnit(PoundForce.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = PoundForce.toSIUnit(Inch.toSIUnit(value))
}
@Serializable
object InchOunceForce : ImperialEnergy() {
    override val symbol: String = "inozf"
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Energy
    override fun fromSIUnit(value: Decimal): Decimal = Inch.fromSIUnit(OunceForce.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = OunceForce.toSIUnit(Inch.toSIUnit(value))
}
@Serializable
object HorsepowerHour : ImperialEnergy() {
    override val symbol: String = "hph"
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Energy
    override fun fromSIUnit(value: Decimal): Decimal = WattHour.fromSIUnit(Horsepower.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = Horsepower.toSIUnit(WattHour.toSIUnit(value))
}
@Serializable
object BritishThermalUnit : ImperialEnergy(), SystemScientificUnit<MeasurementSystem.Imperial, PhysicalQuantity.Energy> by BritishThermalUnitBase(Calorie.IT) {

    internal class BritishThermalUnitBase(private val calorieUnit: CalorieUnit, symbolPostfix: String = "") : SystemScientificUnit<MeasurementSystem.Imperial, PhysicalQuantity.Energy> {
        override val symbol: String = "Btu$symbolPostfix"
        override val system = MeasurementSystem.Imperial
        override val quantity = PhysicalQuantity.Energy
        override fun fromSIUnit(value: Decimal): Decimal = Pound.fromSIUnit(Rankine.fromSIUnit(Kilo(calorieUnit).fromSIUnit(value)))
        override fun toSIUnit(value: Decimal): Decimal = Kilo(calorieUnit).toSIUnit(Rankine.toSIUnit(Pound.toSIUnit(value)))
    }

    @Serializable
    object Thermal : ImperialEnergy(), SystemScientificUnit<MeasurementSystem.Imperial, PhysicalQuantity.Energy> by BritishThermalUnitBase(Calorie, "-th")
}

@Serializable
data class MetricMetricAndImperialEnergyWrapper(val metricAndImperialEnergy: MetricAndImperialEnergy) : MetricEnergy() {
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Energy
    override val symbol: String = metricAndImperialEnergy.symbol
    override fun fromSIUnit(value: Decimal): Decimal = metricAndImperialEnergy.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = metricAndImperialEnergy.toSIUnit(value)
}

val <EnergyUnit : MetricAndImperialEnergy> EnergyUnit.metric get() = MetricMetricAndImperialEnergyWrapper(this)

@Serializable
data class ImperialMetricAndImperialEnergyWrapper(val metricAndImperialEnergy: MetricAndImperialEnergy) : ImperialEnergy() {
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Energy
    override val symbol: String = metricAndImperialEnergy.symbol
    override fun fromSIUnit(value: Decimal): Decimal = metricAndImperialEnergy.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = metricAndImperialEnergy.toSIUnit(value)
}

val <EnergyUnit : MetricAndImperialEnergy> EnergyUnit.imperial get() = ImperialMetricAndImperialEnergyWrapper(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.convertValue
import com.splendo.kaluga.scientific.invoke
import kotlinx.serialization.Serializable

val MetricForceUnits: Set<MetricForce> get() = setOf(
    Newton,
    Nanonewton,
    Micronewton,
    Millinewton,
    Centinewton,
    Decinewton,
    Decanewton,
    Hectonewton,
    Kilonewton,
    Meganewton,
    Giganewton,
    Dyne,
    Nanodyne,
    Microdyne,
    Millidyne,
    Centidyne,
    Decidyne,
    Decadyne,
    Hectodyne,
    Kilodyne,
    Megadyne,
    Gigadyne,
    KilogramForce,
    TonneForce,
    GramForce,
    MilligramForce
)

val ImperialForceUnits: Set<ImperialForce> get() = setOf(
    Poundal,
    PoundForce,
    OunceForce,
    GrainForce,
)

val USCustomaryForceUnits: Set<USCustomaryForce> get() = setOf(
    Kip,
    UsTonForce
) + ImperialForceUnits.map { it.usCustomary }

val UKImperialForceUnits: Set<UKImperialForce> get() = setOf(
    ImperialTonForce
) + ImperialForceUnits.map { it.ukImperial }

val ForceUnits: Set<Force> get() = MetricForceUnits + ImperialForceUnits + UKImperialForceUnits.filter { it !is UKImperialImperialForceWrapper }.toSet() + USCustomaryForceUnits.filter { it !is USCustomaryImperialForceWrapper }

@Serializable
sealed class Force : AbstractScientificUnit<PhysicalQuantity.Force>()

@Serializable
sealed class MetricForce : Force(), MetricScientificUnit<PhysicalQuantity.Force>

@Serializable
sealed class ImperialForce : Force(), ImperialScientificUnit<PhysicalQuantity.Force> {
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Force
}

@Serializable
sealed class USCustomaryForce : Force(), USCustomaryScientificUnit<PhysicalQuantity.Force> {
    override val system = MeasurementSystem.USCustomary
    override val quantity = PhysicalQuantity.Force
}

@Serializable
sealed class UKImperialForce : Force(), UKImperialScientificUnit<PhysicalQuantity.Force> {
    override val system = MeasurementSystem.UKImperial
    override val quantity = PhysicalQuantity.Force
}

@Serializable
object Newton : MetricForce(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Force> {
    override val symbol: String = "N"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Force
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanonewton : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Newton> by Nano(Newton)
@Serializable
object Micronewton : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Newton> by Micro(Newton)
@Serializable
object Millinewton : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Newton> by Milli(Newton)
@Serializable
object Centinewton : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Newton> by Centi(Newton)
@Serializable
object Decinewton : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Newton> by Deci(Newton)
@Serializable
object Decanewton : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Newton> by Deca(Newton)
@Serializable
object Hectonewton : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Newton> by Hecto(Newton)
@Serializable
object Kilonewton : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Newton> by Kilo(Newton)
@Serializable
object Meganewton : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Newton> by Mega(Newton)
@Serializable
object Giganewton : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Newton> by Giga(Newton)

@Serializable
object Dyne : MetricForce(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Force> {
    override val symbol: String = "dyn"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Force
    override fun fromSIUnit(value: Decimal): Decimal = value * 100000.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value / 100000.toDecimal()
}

@Serializable
object Nanodyne : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> by Nano(Dyne)
@Serializable
object Microdyne : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> by Micro(Dyne)
@Serializable
object Millidyne : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> by Milli(Dyne)
@Serializable
object Centidyne : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> by Centi(Dyne)
@Serializable
object Decidyne : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> by Deci(Dyne)
@Serializable
object Decadyne : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> by Deca(Dyne)
@Serializable
object Hectodyne : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> by Hecto(Dyne)
@Serializable
object Kilodyne : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> by Kilo(Dyne)
@Serializable
object Megadyne : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> by Mega(Dyne)
@Serializable
object Gigadyne : MetricForce(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> by Giga(Dyne)

@Serializable
object KilogramForce : MetricForce() {
    override val symbol: String = "kgf"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Force
    override fun fromSIUnit(value: Decimal): Decimal = value / MetricStandardGravityAcceleration.decimalValue
    override fun toSIUnit(value: Decimal): Decimal = value * MetricStandardGravityAcceleration.decimalValue
}
@Serializable
object TonneForce : MetricForce() {
    override val symbol: String = "tf"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Force
    override fun fromSIUnit(value: Decimal): Decimal = Tonne.fromSIUnit(value) / MetricStandardGravityAcceleration.decimalValue
    override fun toSIUnit(value: Decimal): Decimal = Tonne.toSIUnit(value) * MetricStandardGravityAcceleration.decimalValue
}
@Serializable
object GramForce : MetricForce() {
    override val symbol: String = "gf"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Force
    override fun fromSIUnit(value: Decimal): Decimal = Gram.fromSIUnit(value) / MetricStandardGravityAcceleration.decimalValue
    override fun toSIUnit(value: Decimal): Decimal = Gram.toSIUnit(value) * MetricStandardGravityAcceleration.decimalValue
}
@Serializable
object MilligramForce : MetricForce() {
    override val symbol: String = "gf"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Force
    override fun fromSIUnit(value: Decimal): Decimal = Milligram.fromSIUnit(value) / MetricStandardGravityAcceleration.decimalValue
    override fun toSIUnit(value: Decimal): Decimal = Milligram.toSIUnit(value) * MetricStandardGravityAcceleration.decimalValue
}

// Imperial
@Serializable
object Poundal : ImperialForce() {
    override val symbol: String = "pdl"
    override fun fromSIUnit(value: Decimal): Decimal = Pound.fromSIUnit(value) * 1(Meter per Second per Second).convertValue(Foot per Second per Second)
    override fun toSIUnit(value: Decimal): Decimal = Pound.toSIUnit(value) * 1(Foot per Second per Second).convertValue(Meter per Second per Second)
}

@Serializable
object PoundForce : ImperialForce() {
    override val symbol: String = "lbf"
    override fun fromSIUnit(value: Decimal): Decimal = Pound.fromSIUnit(value) / MetricStandardGravityAcceleration.decimalValue
    override fun toSIUnit(value: Decimal): Decimal = Pound.toSIUnit(value) * MetricStandardGravityAcceleration.decimalValue
}

@Serializable
object OunceForce : ImperialForce() {
    override val symbol: String = "ozf"
    override fun fromSIUnit(value: Decimal): Decimal = Ounce.fromSIUnit(value) / MetricStandardGravityAcceleration.decimalValue
    override fun toSIUnit(value: Decimal): Decimal = Ounce.toSIUnit(value) * MetricStandardGravityAcceleration.decimalValue
}

@Serializable
object GrainForce : ImperialForce() {
    override val symbol: String = "grf"
    override fun fromSIUnit(value: Decimal): Decimal = Grain.fromSIUnit(value) / MetricStandardGravityAcceleration.decimalValue
    override fun toSIUnit(value: Decimal): Decimal = Grain.toSIUnit(value) * MetricStandardGravityAcceleration.decimalValue
}

@Serializable
object Kip : USCustomaryForce() {
    private const val POUNDS_FORCE_IN_KIP = 1000.0
    override val symbol: String = "kip"
    override fun fromSIUnit(value: Decimal): Decimal = PoundForce.fromSIUnit(value) / POUNDS_FORCE_IN_KIP.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = PoundForce.toSIUnit(value * POUNDS_FORCE_IN_KIP.toDecimal())
}

@Serializable
object UsTonForce : USCustomaryForce() {
    override val symbol: String = "STf"
    override fun fromSIUnit(value: Decimal): Decimal = UsTon.fromSIUnit(value) / MetricStandardGravityAcceleration.decimalValue
    override fun toSIUnit(value: Decimal): Decimal = UsTon.toSIUnit(value) * MetricStandardGravityAcceleration.decimalValue
}

@Serializable
data class USCustomaryImperialForceWrapper(val imperial: ImperialForce) : USCustomaryForce() {
    override val symbol: String = imperial.symbol
    override fun fromSIUnit(value: Decimal): Decimal = imperial.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = imperial.toSIUnit(value)
}

val <ForceUnit : ImperialForce> ForceUnit.usCustomary get() = USCustomaryImperialForceWrapper(this)

@Serializable
object ImperialTonForce : UKImperialForce() {
    override val symbol: String = "LTf"
    override fun fromSIUnit(value: Decimal): Decimal = ImperialTon.fromSIUnit(value) / MetricStandardGravityAcceleration.decimalValue
    override fun toSIUnit(value: Decimal): Decimal = ImperialTon.toSIUnit(value) * MetricStandardGravityAcceleration.decimalValue
}

@Serializable
data class UKImperialImperialForceWrapper(val imperial: ImperialForce) : UKImperialForce() {
    override val symbol: String = imperial.symbol
    override fun fromSIUnit(value: Decimal): Decimal = imperial.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = imperial.toSIUnit(value)
}

val <ForceUnit : ImperialForce> ForceUnit.ukImperial get() = UKImperialImperialForceWrapper(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val FrequencyUnits: Set<Frequency> get() = setOf(
    Hertz,
    Nanohertz,
    Microhertz,
    Millihertz,
    Centihertz,
    Decihertz,
    Decahertz,
    Hectohertz,
    Kilohertz,
    Megahertz,
    Gigahertz,
    BeatsPerMinute
)

@Serializable
sealed class Frequency : ScientificUnit<PhysicalQuantity.Frequency>, MetricAndImperialScientificUnit<PhysicalQuantity.Frequency>

object Hertz : Frequency(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Frequency> {
    override val symbol: String = "Hz"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Frequency
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanohertz : Frequency(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Frequency, Hertz> by Nano(Hertz)
@Serializable
object Microhertz : Frequency(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Frequency, Hertz> by Micro(Hertz)
@Serializable
object Millihertz : Frequency(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Frequency, Hertz> by Milli(Hertz)
@Serializable
object Centihertz : Frequency(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Frequency, Hertz> by Centi(Hertz)
@Serializable
object Decihertz : Frequency(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Frequency, Hertz> by Deci(Hertz)
@Serializable
object Decahertz : Frequency(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Frequency, Hertz> by Deca(Hertz)
@Serializable
object Hectohertz : Frequency(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Frequency, Hertz> by Hecto(Hertz)
@Serializable
object Kilohertz : Frequency(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Frequency, Hertz> by Kilo(Hertz)
@Serializable
object Megahertz : Frequency(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Frequency, Hertz> by Mega(Hertz)
@Serializable
object Gigahertz : Frequency(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Frequency, Hertz> by Giga(Hertz)
@Serializable
object BeatsPerMinute : Frequency() {
    override val symbol: String = "bpm"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Frequency
    override fun fromSIUnit(value: Decimal): Decimal = value * 60.0.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value / 60.0.toDecimal()
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricAndUKImperialHeatCapacityUnits: Set<MetricAndUKImperialHeatCapacity> get() = MetricAndImperialEnergyUnits.flatMap { energy ->
    MetricAndUkImperialTemperatureUnits.map { energy per it }
}.toSet()

val MetricHeatCapacityUnits: Set<MetricHeatCapacity> get() = MetricEnergyUnits.flatMap { energy ->
    MetricAndUkImperialTemperatureUnits.map { energy per it }
}.toSet()

val UKImperialHeatCapacityUnits: Set<UKImperialHeatCapacity> get() = ImperialEnergyUnits.flatMap { energy ->
    MetricAndUkImperialTemperatureUnits.map { energy per it }
}.toSet()

val USCustomaryHeatCapacityUnits: Set<USCustomaryHeatCapacity> get() = ImperialEnergyUnits.flatMap { energy ->
    USCustomaryTemperatureUnits.map { energy per it }
}.toSet()

val HeatCapacityUnits: Set<HeatCapacity> get() = MetricAndUKImperialHeatCapacityUnits +
    MetricHeatCapacityUnits.filter { it.energy !is MetricMetricAndImperialEnergyWrapper }.toSet() +
    UKImperialHeatCapacityUnits.filter { it.energy !is ImperialMetricAndImperialEnergyWrapper }.toSet() +
    USCustomaryHeatCapacityUnits

@Serializable
sealed class HeatCapacity : AbstractScientificUnit<PhysicalQuantity.HeatCapacity>() {
    abstract val energy: Energy
    abstract val per: Temperature
    override val quantity = PhysicalQuantity.HeatCapacity
    override val symbol: String by lazy { "${energy.symbol}/${per.symbol}" }
    override fun fromSIUnit(value: Decimal): Decimal = per.deltaToSIUnitDelta(energy.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = energy.toSIUnit(per.deltaFromSIUnitDelta(value))
}

@Serializable
data class MetricAndUKImperialHeatCapacity(override val energy: MetricAndImperialEnergy, override val per: MetricAndUKImperialTemperature) : HeatCapacity(), MetricAndUKImperialScientificUnit<PhysicalQuantity.HeatCapacity> {
    override val system = MeasurementSystem.MetricAndUKImperial
    val metric get() = energy.metric per per
    val ukImperial get() = energy.imperial per per
}
@Serializable
data class MetricHeatCapacity(override val energy: MetricEnergy, override val per: MetricAndUKImperialTemperature) : HeatCapacity(), MetricScientificUnit<PhysicalQuantity.HeatCapacity> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class UKImperialHeatCapacity(override val energy: ImperialEnergy, override val per: MetricAndUKImperialTemperature) : HeatCapacity(), UKImperialScientificUnit<PhysicalQuantity.HeatCapacity> {
    override val system = MeasurementSystem.UKImperial
}
@Serializable
data class USCustomaryHeatCapacity(override val energy: ImperialEnergy, override val per: USCustomaryTemperature) : HeatCapacity(), USCustomaryScientificUnit<PhysicalQuantity.HeatCapacity> {
    override val system = MeasurementSystem.USCustomary
}

infix fun MetricAndImperialEnergy.per(temperature: MetricAndUKImperialTemperature) = MetricAndUKImperialHeatCapacity(this, temperature)
infix fun MetricEnergy.per(temperature: MetricAndUKImperialTemperature) = MetricHeatCapacity(this, temperature)
infix fun ImperialEnergy.per(temperature: MetricAndUKImperialTemperature) = UKImperialHeatCapacity(this, temperature)
infix fun MetricAndImperialEnergy.per(temperature: USCustomaryTemperature) = USCustomaryHeatCapacity(this.imperial, temperature)
infix fun ImperialEnergy.per(temperature: USCustomaryTemperature) = USCustomaryHeatCapacity(this, temperature)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricIlluminanceUnits: Set<MetricIlluminance> get() = setOf(
    Lux,
    Nanolux,
    Microlux,
    Millilux,
    Centilux,
    Decilux,
    Decalux,
    Hectolux,
    Kilolux,
    Megalux,
    Gigalux,
    Phot,
    Nanophot,
    Microphot,
    Milliphot,
    Centiphot,
    Deciphot,
    Decaphot,
    Hectophot,
    Kilophot,
    Megaphot,
    Gigaphot
)

val ImperialIlluminanceUnits: Set<ImperialIlluminance> get() = setOf(
    FootCandle
)

val IlluminanceUnits: Set<Illuminance> get() = MetricIlluminanceUnits + ImperialIlluminanceUnits

@Serializable
sealed class Illuminance : AbstractScientificUnit<PhysicalQuantity.Illuminance>()

@Serializable
sealed class MetricIlluminance : Illuminance(), MetricScientificUnit<PhysicalQuantity.Illuminance>
@Serializable
sealed class ImperialIlluminance : Illuminance(), ImperialScientificUnit<PhysicalQuantity.Illuminance>

@Serializable
object Lux : MetricIlluminance(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance> {
    override val symbol = "lx"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Illuminance
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanolux : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Lux> by Nano(Lux)
@Serializable
object Microlux : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Lux> by Micro(Lux)
@Serializable
object Millilux : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Lux> by Milli(Lux)
@Serializable
object Centilux : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Lux> by Centi(Lux)
@Serializable
object Decilux : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Lux> by Deci(Lux)
@Serializable
object Decalux : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Lux> by Deca(Lux)
@Serializable
object Hectolux : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Lux> by Hecto(Lux)
@Serializable
object Kilolux : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Lux> by Kilo(Lux)
@Serializable
object Megalux : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Lux> by Mega(Lux)
@Serializable
object Gigalux : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Lux> by Giga(Lux)

@Serializable
object Phot : MetricIlluminance(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance> {
    override val symbol = "ph"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Illuminance
    override fun fromSIUnit(value: Decimal): Decimal = SquareCentimeter.toSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = SquareCentimeter.fromSIUnit(value)
}

@Serializable
object Nanophot : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Phot> by Nano(Phot)
@Serializable
object Microphot : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Phot> by Micro(Phot)
@Serializable
object Milliphot : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Phot> by Milli(Phot)
@Serializable
object Centiphot : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Phot> by Centi(Phot)
@Serializable
object Deciphot : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Phot> by Deci(Phot)
@Serializable
object Decaphot : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Phot> by Deca(Phot)
@Serializable
object Hectophot : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Phot> by Hecto(Phot)
@Serializable
object Kilophot : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Phot> by Kilo(Phot)
@Serializable
object Megaphot : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Phot> by Mega(Phot)
@Serializable
object Gigaphot : MetricIlluminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Phot> by Giga(Phot)

@Serializable
object FootCandle : ImperialIlluminance() {
    override val symbol: String = "fc"
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Illuminance
    override fun fromSIUnit(value: Decimal): Decimal = SquareFoot.toSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = SquareFoot.fromSIUnit(value)
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val IonizingRadiationAbsorbedDoseUnits: Set<IonizingRadiationAbsorbedDose> get() = setOf(
    Gray,
    Nanogray,
    Microgray,
    Milligray,
    Centigray,
    Decigray,
    Decagray,
    Hectogray,
    Kilogray,
    Megagray,
    Gigagray,
    Rad,
    Nanorad,
    Microrad,
    Millirad,
    Centirad,
    Decirad,
    Decarad,
    Hectorad,
    Kilorad,
    Megarad,
    Gigarad
)

@Serializable
sealed class IonizingRadiationAbsorbedDose : AbstractScientificUnit<PhysicalQuantity.IonizingRadiationAbsorbedDose>(), MetricAndImperialScientificUnit<PhysicalQuantity.IonizingRadiationAbsorbedDose>

@Serializable
object Gray : IonizingRadiationAbsorbedDose(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose> {
    override val symbol = "Gy"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.IonizingRadiationAbsorbedDose
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanogray : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Gray> by Nano(Gray)
@Serializable
object Microgray : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Gray> by Micro(Gray)
@Serializable
object Milligray : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Gray> by Milli(Gray)
@Serializable
object Centigray : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Gray> by Centi(Gray)
@Serializable
object Decigray : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Gray> by Deci(Gray)
@Serializable
object Decagray : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Gray> by Deca(Gray)
@Serializable
object Hectogray : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Gray> by Hecto(Gray)
@Serializable
object Kilogray : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Gray> by Kilo(Gray)
@Serializable
object Megagray : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Gray> by Mega(Gray)
@Serializable
object Gigagray : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Gray> by Giga(Gray)

@Serializable
object Rad : IonizingRadiationAbsorbedDose(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose> {
    const val GRAY_IN_RAD = 0.01
    override val symbol = "rad"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.IonizingRadiationAbsorbedDose
    override fun fromSIUnit(value: Decimal): Decimal = value / GRAY_IN_RAD.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value * GRAY_IN_RAD.toDecimal()
}

@Serializable
object Nanorad : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad> by Nano(Rad)
@Serializable
object Microrad : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad> by Micro(Rad)
@Serializable
object Millirad : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad> by Milli(Rad)
@Serializable
object Centirad : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad> by Centi(Rad)
@Serializable
object Decirad : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad> by Deci(Rad)
@Serializable
object Decarad : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad> by Deca(Rad)
@Serializable
object Hectorad : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad> by Hecto(Rad)
@Serializable
object Kilorad : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad> by Kilo(Rad)
@Serializable
object Megarad : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad> by Mega(Rad)
@Serializable
object Gigarad : IonizingRadiationAbsorbedDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad> by Giga(Rad)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val IonizingRadiationEquivalentDoseUnits: Set<IonizingRadiationEquivalentDose> get() = setOf(
    Sievert,
    Nanosievert,
    Microsievert,
    Millisievert,
    Centisievert,
    Decisievert,
    Decasievert,
    Hectosievert,
    Kilosievert,
    Megasievert,
    Gigasievert,
    RoentgenEquivalentMan,
    NanoroentgenEquivalentMan,
    MicroroentgenEquivalentMan,
    MilliroentgenEquivalentMan,
    CentiroentgenEquivalentMan,
    DeciroentgenEquivalentMan,
    DecaroentgenEquivalentMan,
    HectoroentgenEquivalentMan,
    KiloroentgenEquivalentMan,
    MegaroentgenEquivalentMan,
    GigaroentgenEquivalentMan
)

@Serializable
sealed class IonizingRadiationEquivalentDose : AbstractScientificUnit<PhysicalQuantity.IonizingRadiationEquivalentDose>(), MetricAndImperialScientificUnit<PhysicalQuantity.IonizingRadiationEquivalentDose>

@Serializable
object Sievert : IonizingRadiationEquivalentDose(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose> {
    override val symbol = "Sv"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.IonizingRadiationEquivalentDose
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanosievert : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, Sievert> by Nano(Sievert)
@Serializable
object Microsievert : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, Sievert> by Micro(Sievert)
@Serializable
object Millisievert : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, Sievert> by Milli(Sievert)
@Serializable
object Centisievert : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, Sievert> by Centi(Sievert)
@Serializable
object Decisievert : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, Sievert> by Deci(Sievert)
@Serializable
object Decasievert : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, Sievert> by Deca(Sievert)
@Serializable
object Hectosievert : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, Sievert> by Hecto(Sievert)
@Serializable
object Kilosievert : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, Sievert> by Kilo(Sievert)
@Serializable
object Megasievert : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, Sievert> by Mega(Sievert)
@Serializable
object Gigasievert : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, Sievert> by Giga(Sievert)

@Serializable
object RoentgenEquivalentMan : IonizingRadiationEquivalentDose(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose> {
    const val SIEVERT_IN_REM = 0.01
    override val symbol = "rem"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.IonizingRadiationEquivalentDose
    override fun fromSIUnit(value: Decimal): Decimal = value / SIEVERT_IN_REM.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value * SIEVERT_IN_REM.toDecimal()
}

@Serializable
object NanoroentgenEquivalentMan : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan> by Nano(RoentgenEquivalentMan)
@Serializable
object MicroroentgenEquivalentMan : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan> by Micro(RoentgenEquivalentMan)
@Serializable
object MilliroentgenEquivalentMan : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan> by Milli(RoentgenEquivalentMan)
@Serializable
object CentiroentgenEquivalentMan : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan> by Centi(RoentgenEquivalentMan)
@Serializable
object DeciroentgenEquivalentMan : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan> by Deci(RoentgenEquivalentMan)
@Serializable
object DecaroentgenEquivalentMan : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan> by Deca(RoentgenEquivalentMan)
@Serializable
object HectoroentgenEquivalentMan : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan> by Hecto(RoentgenEquivalentMan)
@Serializable
object KiloroentgenEquivalentMan : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan> by Kilo(RoentgenEquivalentMan)
@Serializable
object MegaroentgenEquivalentMan : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan> by Mega(RoentgenEquivalentMan)
@Serializable
object GigaroentgenEquivalentMan : IonizingRadiationEquivalentDose(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan> by Giga(RoentgenEquivalentMan)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricJoltUnits: Set<MetricJolt> get() = MetricAccelerationUnits.flatMap { acceleration ->
    TimeUnits.map { acceleration per it }
}.toSet()

val ImperialJoltUnits: Set<ImperialJolt> get() = ImperialAccelerationUnits.flatMap { acceleration ->
    TimeUnits.map { acceleration per it }
}.toSet()

val JoltUnits: Set<Jolt> get() = MetricJoltUnits +
    ImperialJoltUnits

@Serializable
sealed class Jolt : AbstractScientificUnit<PhysicalQuantity.Jolt>() {
    abstract val acceleration: Acceleration
    abstract val per: Time
    override val quantity = PhysicalQuantity.Jolt
    override val symbol: String by lazy {
        val perSymbol = when {
            per == acceleration.per && acceleration.speed.per == per -> "${per.symbol}3"
            per == acceleration.per -> "${per.symbol}2*${acceleration.speed.per}"
            per == acceleration.speed.per -> "${per.symbol}2*${acceleration.per}"
            acceleration.per == acceleration.speed.per -> "${per.symbol}*${acceleration.per}2"
            else -> "${per.symbol}*${acceleration.per.symbol}*${acceleration.speed.per.symbol}"
        }
        "${acceleration.speed.distance.symbol} / $perSymbol"
    }
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(acceleration.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = acceleration.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricJolt(override val acceleration: MetricAcceleration, override val per: Time) : Jolt(), MetricScientificUnit<PhysicalQuantity.Jolt> {
    override val system = MeasurementSystem.Metric
}

@Serializable
data class ImperialJolt(override val acceleration: ImperialAcceleration, override val per: Time) : Jolt(), ImperialScientificUnit<PhysicalQuantity.Jolt> {
    override val system = MeasurementSystem.Imperial
}

infix fun MetricAcceleration.per(time: Time) = MetricJolt(this, time)
infix fun ImperialAcceleration.per(time: Time) = ImperialJolt(this, time)
infix fun Acceleration.per(time: Time): Jolt = when (this) {
    is MetricAcceleration -> this per time
    is ImperialAcceleration -> this per time
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricLengthUnits: Set<MetricLength> get() = setOf(
    Meter,
    Nanometer,
    Micrometer,
    Millimeter,
    Centimeter,
    Decimeter,
    Decameter,
    Hectometer,
    Kilometer,
    Megameter,
    Gigameter,
    NauticalMile
)

val ImperialLengthUnits: Set<ImperialLength> get() = setOf(
    Inch,
    Foot,
    Yard,
    Mile
)

val LengthUnits: Set<Length> get() = MetricLengthUnits + ImperialLengthUnits

@Serializable
sealed class Length : AbstractScientificUnit<PhysicalQuantity.Length>()

@Serializable
sealed class MetricLength : Length(), MetricScientificUnit<PhysicalQuantity.Length>

@Serializable
sealed class ImperialLength(override val symbol: String) : Length(), ImperialScientificUnit<PhysicalQuantity.Length> {
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Length
}

// Metric Length
@Serializable
object Meter : MetricLength(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Length> {
    override val symbol: String = "m"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Length
    override fun toSIUnit(value: Decimal): Decimal = value
    override fun fromSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanometer : MetricLength(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Length, Meter> by Nano(Meter)

@Serializable
object Micrometer : MetricLength(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Length, Meter> by Micro(Meter)

@Serializable
object Millimeter : MetricLength(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Length, Meter> by Milli(Meter)

@Serializable
object Centimeter : MetricLength(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Length, Meter> by Centi(Meter)

@Serializable
object Decimeter : MetricLength(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Length, Meter> by Deci(Meter)

@Serializable
object Decameter : MetricLength(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Length, Meter> by Deca(Meter)

@Serializable
object Hectometer : MetricLength(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Length, Meter> by Hecto(Meter)

@Serializable
object Kilometer : MetricLength(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Length, Meter> by Kilo(Meter)

@Serializable
object Megameter : MetricLength(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Length, Meter> by Mega(Meter)

@Serializable
object Gigameter : MetricLength(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Length, Meter> by Giga(Meter)

@Serializable
object NauticalMile : MetricLength() {
    private const val METER_IN_NAUTICAL_MILE = 1852
    override val symbol: String = "nmi"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Length
    override fun toSIUnit(value: Decimal): Decimal = value * METER_IN_NAUTICAL_MILE.toDecimal()
    override fun fromSIUnit(value: Decimal): Decimal = value / METER_IN_NAUTICAL_MILE.toDecimal()
}

// Imperial Length
@Serializable
object Inch : ImperialLength("in") {
    private const val INCHES_IN_FOOT = 12
    override fun toSIUnit(value: Decimal): Decimal = Foot.toSIUnit(value / INCHES_IN_FOOT.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = Foot.fromSIUnit(value) * INCHES_IN_FOOT.toDecimal()
}

@Serializable
object Foot : ImperialLength("ft") {
    private const val METER_IN_FEET = 0.3048
    override fun toSIUnit(value: Decimal): Decimal = value * METER_IN_FEET.toDecimal()
    override fun fromSIUnit(value: Decimal): Decimal = value / METER_IN_FEET.toDecimal()
}

@Serializable
object Yard : ImperialLength("yd") {
    private const val FOOT_IN_YARD = 3
    override fun toSIUnit(value: Decimal): Decimal = Foot.toSIUnit(value * FOOT_IN_YARD.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = Foot.fromSIUnit(value) / FOOT_IN_YARD.toDecimal()
}

@Serializable
object Mile : ImperialLength("mi") {
    private const val YARDS_IN_MILE = 1760
    override fun toSIUnit(value: Decimal): Decimal = Yard.toSIUnit(value * YARDS_IN_MILE.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = Yard.fromSIUnit(value) / YARDS_IN_MILE.toDecimal()
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricLinearMassDensityUnits: Set<MetricLinearMassDensity> get() = MetricWeightUnits.flatMap { weight ->
    MetricLengthUnits.map { weight per it }
}.toSet()

val ImperialLinearMassDensityUnits: Set<ImperialLinearMassDensity> get() = ImperialWeightUnits.flatMap { weight ->
    ImperialLengthUnits.map { weight per it }
}.toSet()

val UKImperialLinearMassDensityUnits: Set<UKImperialLinearMassDensity> get() = UKImperialWeightUnits.flatMap { weight ->
    ImperialLengthUnits.map { weight per it }
}.toSet()

val USCustomaryLinearMassDensityUnits: Set<USCustomaryLinearMassDensity> get() = USCustomaryWeightUnits.flatMap { weight ->
    ImperialLengthUnits.map { weight per it }
}.toSet()

val LinearMassDensityUnits: Set<LinearMassDensity> get() = MetricLinearMassDensityUnits +
    ImperialLinearMassDensityUnits +
    UKImperialLinearMassDensityUnits.filter { it.weight !is UKImperialImperialWeightWrapper }.toSet() +
    USCustomaryLinearMassDensityUnits.filter { it.weight !is USCustomaryImperialWeightWrapper }.toSet()

@Serializable
sealed class LinearMassDensity : AbstractScientificUnit<PhysicalQuantity.LinearMassDensity>() {
    abstract val weight: Weight
    abstract val per: Length
    override val symbol: String by lazy { "${weight.symbol} / ${per.symbol}" }
    override val quantity = PhysicalQuantity.LinearMassDensity
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(weight.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = weight.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricLinearMassDensity(override val weight: MetricWeight, override val per: MetricLength) : LinearMassDensity(), MetricScientificUnit<PhysicalQuantity.LinearMassDensity> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialLinearMassDensity(override val weight: ImperialWeight, override val per: ImperialLength) : LinearMassDensity(), ImperialScientificUnit<PhysicalQuantity.LinearMassDensity> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = weight.ukImperial per per
    val usCustomary get() = weight.usCustomary per per
}
@Serializable
data class USCustomaryLinearMassDensity(override val weight: USCustomaryWeight, override val per: ImperialLength) : LinearMassDensity(), USCustomaryScientificUnit<PhysicalQuantity.LinearMassDensity> {
    override val system = MeasurementSystem.USCustomary
}
@Serializable
data class UKImperialLinearMassDensity(override val weight: UKImperialWeight, override val per: ImperialLength) : LinearMassDensity(), UKImperialScientificUnit<PhysicalQuantity.LinearMassDensity> {
    override val system = MeasurementSystem.UKImperial
}

infix fun MetricWeight.per(length: MetricLength) = MetricLinearMassDensity(this, length)
infix fun ImperialWeight.per(length: ImperialLength) = ImperialLinearMassDensity(this, length)
infix fun USCustomaryWeight.per(length: ImperialLength) = USCustomaryLinearMassDensity(this, length)
infix fun UKImperialWeight.per(length: ImperialLength) = UKImperialLinearMassDensity(this, length)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable
import kotlin.math.PI

val MetricLuminanceUnits: Set<MetricLuminance> get() = setOf(
    Nit,
    Nanonit,
    Micronit,
    Millinit,
    Centinit,
    Decinit,
    Decanit,
    Hectonit,
    Kilonit,
    Meganit,
    Giganit,
    Stilb,
    Apostilb,
    Lambert,
    Skot,
    Bril
)

val ImperialLuminanceUnits: Set<ImperialLuminance> get() = setOf(
    FootLambert
)

val LuminanceUnits: Set<Luminance> get() = MetricLuminanceUnits + ImperialLuminanceUnits

@Serializable
sealed class Luminance : AbstractScientificUnit<PhysicalQuantity.Luminance>()

@Serializable
sealed class MetricLuminance : Luminance(), MetricScientificUnit<PhysicalQuantity.Luminance>
@Serializable
sealed class ImperialLuminance : Luminance(), ImperialScientificUnit<PhysicalQuantity.Luminance>

@Serializable
object Nit : MetricLuminance(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Luminance> {
    override val symbol: String = "nt"
    override val quantity = PhysicalQuantity.Luminance
    override val system = MeasurementSystem.Metric
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanonit : MetricLuminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Luminance, Nit> by Nano(Nit)
@Serializable
object Micronit : MetricLuminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Luminance, Nit> by Micro(Nit)
@Serializable
object Millinit : MetricLuminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Luminance, Nit> by Milli(Nit)
@Serializable
object Centinit : MetricLuminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Luminance, Nit> by Centi(Nit)
@Serializable
object Decinit : MetricLuminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Luminance, Nit> by Deci(Nit)
@Serializable
object Decanit : MetricLuminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Luminance, Nit> by Deca(Nit)
@Serializable
object Hectonit : MetricLuminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Luminance, Nit> by Hecto(Nit)
@Serializable
object Kilonit : MetricLuminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Luminance, Nit> by Kilo(Nit)
@Serializable
object Meganit : MetricLuminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Luminance, Nit> by Mega(Nit)
@Serializable
object Giganit : MetricLuminance(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Luminance, Nit> by Giga(Nit)

@Serializable
object Stilb : MetricLuminance() {
    override val symbol: String = "sb"
    override val quantity = PhysicalQuantity.Luminance
    override val system = MeasurementSystem.Metric
    override fun fromSIUnit(value: Decimal): Decimal = SquareCentimeter.toSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = SquareCentimeter.fromSIUnit(value)
}

@Serializable
object Apostilb : MetricLuminance() {
    private const val APOSTILB_IN_NIT = PI
    override val symbol: String = "asb"
    override val quantity = PhysicalQuantity.Luminance
    override val system = MeasurementSystem.Metric
    override fun fromSIUnit(value: Decimal): Decimal = value * APOSTILB_IN_NIT.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value / APOSTILB_IN_NIT.toDecimal()
}

@Serializable
object Lambert : MetricLuminance() {
    override val symbol: String = "L"
    override val quantity = PhysicalQuantity.Luminance
    override val system = MeasurementSystem.Metric
    override fun fromSIUnit(value: Decimal): Decimal = Apostilb.fromSIUnit(Stilb.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = Stilb.toSIUnit(Apostilb.toSIUnit(value))
}

@Serializable
object Skot : MetricLuminance() {
    private const val SKOT_IN_APOSTILB = 1000.0
    override val symbol: String = "sk"
    override val quantity = PhysicalQuantity.Luminance
    override val system = MeasurementSystem.Metric
    override fun fromSIUnit(value: Decimal): Decimal = Apostilb.fromSIUnit(value) * SKOT_IN_APOSTILB.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = Apostilb.toSIUnit(value / SKOT_IN_APOSTILB.toDecimal())
}

@Serializable
object Bril : MetricLuminance() {
    private const val BRIL_IN_APOSTILB = 10000000.0
    override val symbol: String = "Bril"
    override val quantity = PhysicalQuantity.Luminance
    override val system = MeasurementSystem.Metric
    override fun fromSIUnit(value: Decimal): Decimal = Apostilb.fromSIUnit(value) * BRIL_IN_APOSTILB.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = Apostilb.toSIUnit(value / BRIL_IN_APOSTILB.toDecimal())
}

@Serializable
object FootLambert : ImperialLuminance() {
    override val symbol: String = "fL"
    override val quantity = PhysicalQuantity.Luminance
    override val system = MeasurementSystem.Imperial
    override fun fromSIUnit(value: Decimal): Decimal = Apostilb.fromSIUnit(SquareFoot.toSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = SquareFoot.fromSIUnit(Apostilb.toSIUnit(value))
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val LuminousEnergyUnits: Set<LuminousEnergy> get() = LuminousFluxUnits.flatMap { flux ->
    TimeUnits.map { flux x it }
}.toSet()

@Serializable
data class LuminousEnergy(val luminousFlux: LuminousFlux, val time: Time) : MetricAndImperialScientificUnit<PhysicalQuantity.LuminousEnergy> {
    override val symbol: String = "${luminousFlux.symbol}⋅${time.symbol}"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.LuminousEnergy
    override fun fromSIUnit(value: Decimal): Decimal = luminousFlux.fromSIUnit(time.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = time.toSIUnit(luminousFlux.toSIUnit(value))
}

infix fun LuminousFlux.x(time: Time) = LuminousEnergy(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricLuminousExposureUnits: Set<MetricLuminousExposure> get() = MetricIlluminanceUnits.flatMap { illuminance ->
    TimeUnits.map { illuminance x it }
}.toSet()

val ImperialLuminousExposureUnits: Set<ImperialLuminousExposure> get() = ImperialIlluminanceUnits.flatMap { illuminance ->
    TimeUnits.map { illuminance x it }
}.toSet()

val LuminousExposureUnits: Set<LuminousExposure> get() = MetricLuminousExposureUnits + ImperialLuminousExposureUnits

@Serializable
abstract class LuminousExposure : ScientificUnit<PhysicalQuantity.LuminousExposure> {
    abstract val illuminance: Illuminance
    abstract val time: Time
    override val symbol: String get() = "${illuminance.symbol}⋅${time.symbol}"
    override val quantity = PhysicalQuantity.LuminousExposure
    override fun fromSIUnit(value: Decimal): Decimal = illuminance.fromSIUnit(time.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = time.toSIUnit(illuminance.toSIUnit(value))
}

@Serializable
data class MetricLuminousExposure(override val illuminance: MetricIlluminance, override val time: Time) : LuminousExposure(), MetricScientificUnit<PhysicalQuantity.LuminousExposure> {
    override val system = MeasurementSystem.Metric
}

@Serializable
data class ImperialLuminousExposure(override val illuminance: ImperialIlluminance, override val time: Time) : LuminousExposure(), ImperialScientificUnit<PhysicalQuantity.LuminousExposure> {
    override val system = MeasurementSystem.Imperial
}

infix fun MetricIlluminance.x(time: Time) = MetricLuminousExposure(this, time)
infix fun ImperialIlluminance.x(time: Time) = ImperialLuminousExposure(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val LuminousFluxUnits: Set<LuminousFlux> get() = setOf(
    Lumen,
    Nanolumen,
    Microlumen,
    Millilumen,
    Centilumen,
    Decilumen,
    Decalumen,
    Hectolumen,
    Kilolumen,
    Megalumen,
    Gigalumen
)

@Serializable
sealed class LuminousFlux : AbstractScientificUnit<PhysicalQuantity.LuminousFlux>(), MetricAndImperialScientificUnit<PhysicalQuantity.LuminousFlux>

@Serializable
object Lumen : LuminousFlux(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousFlux> {
    override val symbol = "lm"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.LuminousFlux
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanolumen : LuminousFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousFlux, Lumen> by Nano(Lumen)
@Serializable
object Microlumen : LuminousFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousFlux, Lumen> by Micro(Lumen)
@Serializable
object Millilumen : LuminousFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousFlux, Lumen> by Milli(Lumen)
@Serializable
object Centilumen : LuminousFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousFlux, Lumen> by Centi(Lumen)
@Serializable
object Decilumen : LuminousFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousFlux, Lumen> by Deci(Lumen)
@Serializable
object Decalumen : LuminousFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousFlux, Lumen> by Deca(Lumen)
@Serializable
object Hectolumen : LuminousFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousFlux, Lumen> by Hecto(Lumen)
@Serializable
object Kilolumen : LuminousFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousFlux, Lumen> by Kilo(Lumen)
@Serializable
object Megalumen : LuminousFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousFlux, Lumen> by Mega(Lumen)
@Serializable
object Gigalumen : LuminousFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousFlux, Lumen> by Giga(Lumen)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val LuminousIntensityUnits: Set<LuminousIntensity> get() = setOf(
    Candela,
    Nanocandela,
    Microcandela,
    Millicandela,
    Centicandela,
    Decicandela,
    Decacandela,
    Hectocandela,
    Kilocandela,
    Megacandela,
    Gigacandela
)

@Serializable
sealed class LuminousIntensity : AbstractScientificUnit<PhysicalQuantity.LuminousIntensity>(), MetricAndImperialScientificUnit<PhysicalQuantity.LuminousIntensity>

@Serializable
object Candela : LuminousIntensity(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousIntensity> {
    override val symbol = "cd"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.LuminousIntensity
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanocandela : LuminousIntensity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousIntensity, Candela> by Nano(Candela)
@Serializable
object Microcandela : LuminousIntensity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousIntensity, Candela> by Micro(Candela)
@Serializable
object Millicandela : LuminousIntensity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousIntensity, Candela> by Milli(Candela)
@Serializable
object Centicandela : LuminousIntensity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousIntensity, Candela> by Centi(Candela)
@Serializable
object Decicandela : LuminousIntensity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousIntensity, Candela> by Deci(Candela)
@Serializable
object Decacandela : LuminousIntensity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousIntensity, Candela> by Deca(Candela)
@Serializable
object Hectocandela : LuminousIntensity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousIntensity, Candela> by Hecto(Candela)
@Serializable
object Kilocandela : LuminousIntensity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousIntensity, Candela> by Kilo(Candela)
@Serializable
object Megacandela : LuminousIntensity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousIntensity, Candela> by Mega(Candela)
@Serializable
object Gigacandela : LuminousIntensity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.LuminousIntensity, Candela> by Giga(Candela)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MagneticFluxUnits: Set<MagneticFlux> get() = setOf(
    Weber,
    Nanoweber,
    Microweber,
    Milliweber,
    Centiweber,
    Deciweber,
    Decaweber,
    Hectoweber,
    Kiloweber,
    Megaweber,
    Gigaweber,
    Maxwell
)

@Serializable
sealed class MagneticFlux : AbstractScientificUnit<PhysicalQuantity.MagneticFlux>(), MetricAndImperialScientificUnit<PhysicalQuantity.MagneticFlux>

@Serializable
object Weber : MagneticFlux(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticFlux> {
    override val symbol = "Wb"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.MagneticFlux
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanoweber : MagneticFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticFlux, Weber> by Nano(Weber)
@Serializable
object Microweber : MagneticFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticFlux, Weber> by Micro(Weber)
@Serializable
object Milliweber : MagneticFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticFlux, Weber> by Milli(Weber)
@Serializable
object Centiweber : MagneticFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticFlux, Weber> by Centi(Weber)
@Serializable
object Deciweber : MagneticFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticFlux, Weber> by Deci(Weber)
@Serializable
object Decaweber : MagneticFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticFlux, Weber> by Deca(Weber)
@Serializable
object Hectoweber : MagneticFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticFlux, Weber> by Hecto(Weber)
@Serializable
object Kiloweber : MagneticFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticFlux, Weber> by Kilo(Weber)
@Serializable
object Megaweber : MagneticFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticFlux, Weber> by Mega(Weber)
@Serializable
object Gigaweber : MagneticFlux(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticFlux, Weber> by Giga(Weber)
@Serializable
object Maxwell : MagneticFlux() {
    override val symbol = "Mx"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.MagneticFlux
    override fun fromSIUnit(value: Decimal): Decimal = Abvolt.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = Abvolt.toSIUnit(value)
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MagneticInductionUnits: Set<MagneticInduction> get() = setOf(
    Tesla,
    Nanotesla,
    Microtesla,
    Millitesla,
    Centitesla,
    Decitesla,
    Decatesla,
    Hectotesla,
    Kilotesla,
    Megatesla,
    Gigatesla,
    Gauss
)

@Serializable
sealed class MagneticInduction : AbstractScientificUnit<PhysicalQuantity.MagneticInduction>(), MetricAndImperialScientificUnit<PhysicalQuantity.MagneticInduction>

@Serializable
object Tesla : MagneticInduction(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticInduction> {
    override val symbol = "T"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.MagneticInduction
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanotesla : MagneticInduction(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticInduction, Tesla> by Nano(Tesla)
@Serializable
object Microtesla : MagneticInduction(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticInduction, Tesla> by Micro(Tesla)
@Serializable
object Millitesla : MagneticInduction(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticInduction, Tesla> by Milli(Tesla)
@Serializable
object Centitesla : MagneticInduction(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticInduction, Tesla> by Centi(Tesla)
@Serializable
object Decitesla : MagneticInduction(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticInduction, Tesla> by Deci(Tesla)
@Serializable
object Decatesla : MagneticInduction(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticInduction, Tesla> by Deca(Tesla)
@Serializable
object Hectotesla : MagneticInduction(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticInduction, Tesla> by Hecto(Tesla)
@Serializable
object Kilotesla : MagneticInduction(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticInduction, Tesla> by Kilo(Tesla)
@Serializable
object Megatesla : MagneticInduction(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticInduction, Tesla> by Mega(Tesla)
@Serializable
object Gigatesla : MagneticInduction(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.MagneticInduction, Tesla> by Giga(Tesla)
@Serializable
object Gauss : MagneticInduction() {
    private const val GAUSS_IN_TESLA = 10000.0
    override val symbol = "G"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.MagneticInduction
    override fun fromSIUnit(value: Decimal): Decimal = value * GAUSS_IN_TESLA.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value / GAUSS_IN_TESLA.toDecimal()
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricMassFlowRateUnits: Set<MetricMassFlowRate> get() = MetricWeightUnits.flatMap { weight ->
    TimeUnits.map { weight per it }
}.toSet()

val ImperialMassFlowRateUnits: Set<ImperialMassFlowRate> get() = ImperialWeightUnits.flatMap { weight ->
    TimeUnits.map { weight per it }
}.toSet()

val UKImperialMassFlowRateUnits: Set<UKImperialMassFlowRate> get() = UKImperialWeightUnits.flatMap { weight ->
    TimeUnits.map { weight per it }
}.toSet()

val USCustomaryMassFlowRateUnits: Set<USCustomaryMassFlowRate> get() = USCustomaryWeightUnits.flatMap { weight ->
    TimeUnits.map { weight per it }
}.toSet()

val MassFlowRateUnits: Set<MassFlowRate> get() = MetricMassFlowRateUnits +
    ImperialMassFlowRateUnits +
    UKImperialMassFlowRateUnits.filter { it.weight !is UKImperialImperialWeightWrapper }.toSet() +
    USCustomaryMassFlowRateUnits.filter { it.weight !is USCustomaryImperialWeightWrapper }.toSet()

@Serializable
sealed class MassFlowRate : AbstractScientificUnit<PhysicalQuantity.MassFlowRate>() {
    abstract val weight: Weight
    abstract val per: Time
    override val symbol: String by lazy { "${weight.symbol} / ${per.symbol}" }
    override val quantity = PhysicalQuantity.MassFlowRate
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(weight.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = weight.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricMassFlowRate(override val weight: MetricWeight, override val per: Time) : MassFlowRate(), MetricScientificUnit<PhysicalQuantity.MassFlowRate> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialMassFlowRate(override val weight: ImperialWeight, override val per: Time) : MassFlowRate(), ImperialScientificUnit<PhysicalQuantity.MassFlowRate> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = weight.ukImperial per per
    val usCustomary get() = weight.usCustomary per per
}
@Serializable
data class USCustomaryMassFlowRate(override val weight: USCustomaryWeight, override val per: Time) : MassFlowRate(), USCustomaryScientificUnit<PhysicalQuantity.MassFlowRate> {
    override val system = MeasurementSystem.USCustomary
}
@Serializable
data class UKImperialMassFlowRate(override val weight: UKImperialWeight, override val per: Time) : MassFlowRate(), UKImperialScientificUnit<PhysicalQuantity.MassFlowRate> {
    override val system = MeasurementSystem.UKImperial
}

infix fun MetricWeight.per(time: Time) = MetricMassFlowRate(this, time)
infix fun ImperialWeight.per(time: Time) = ImperialMassFlowRate(this, time)
infix fun USCustomaryWeight.per(time: Time) = USCustomaryMassFlowRate(this, time)
infix fun UKImperialWeight.per(time: Time) = UKImperialMassFlowRate(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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" BASISystem,
    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

sealed interface MeasurementUsage {
    interface UsedInMetric
    interface UsedInUKImperial
    interface UsedInUSCustomary
    interface UsedInImperial : UsedInUKImperial, UsedInUSCustomary
    interface UsedInMetricAndUKImperial : UsedInMetric, UsedInUKImperial
    interface UsedInMetricAndImperial : UsedInMetric, UsedInImperial
}

@Serializable
sealed class MeasurementSystem : MeasurementUsage {

    @Serializable
    object Metric : MeasurementSystem(), MeasurementUsage.UsedInMetric
    @Serializable
    object Imperial : MeasurementSystem(), MeasurementUsage.UsedInImperial
    @Serializable
    object USCustomary : MeasurementSystem(), MeasurementUsage.UsedInUSCustomary
    @Serializable
    object UKImperial : MeasurementSystem(), MeasurementUsage.UsedInUKImperial
    @Serializable
    object MetricAndUKImperial : MeasurementSystem(), MeasurementUsage.UsedInMetricAndUKImperial
    @Serializable
    object MetricAndImperial : MeasurementSystem(), MeasurementUsage.UsedInMetricAndImperial
}

interface MetricBaseUnit<System, Quantity : PhysicalQuantity> : SystemScientificUnit<System, Quantity> where System : MeasurementSystem, System : MeasurementUsage.UsedInMetric {
    override val quantity: Quantity
}

interface MetricMultipleUnit<System, Quantity : PhysicalQuantity, Unit : MetricBaseUnit<System, Quantity>> : SystemScientificUnit<System, Quantity> where System : MeasurementSystem, System : MeasurementUsage.UsedInMetric

class Giga<System, Quantity : PhysicalQuantity, Unit : MetricBaseUnit<System, Quantity>>(private val unit: Unit) : MetricMultipleUnit<System, Quantity, Unit> where System : MeasurementSystem, System : MeasurementUsage.UsedInMetric {
    override val symbol: String = "G${unit.symbol}"
    override val system: System = unit.system
    override val quantity: Quantity = unit.quantity
    override fun fromSIUnit(value: Decimal): Decimal = unit.fromSIUnit(value) / 1000000000.0.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = unit.toSIUnit(value) * 1000000000.0.toDecimal()
}

class Mega<System, Quantity : PhysicalQuantity, Unit : MetricBaseUnit<System, Quantity>>(private val unit: Unit) : MetricMultipleUnit<System, Quantity, Unit> where System : MeasurementSystem, System : MeasurementUsage.UsedInMetric {
    override val symbol: String = "M${unit.symbol}"
    override val system: System = unit.system
    override val quantity: Quantity = unit.quantity
    override fun fromSIUnit(value: Decimal): Decimal = unit.fromSIUnit(value) / 1000000.0.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = unit.toSIUnit(value) * 1000000.0.toDecimal()
}

class Kilo<System, Quantity : PhysicalQuantity, Unit : MetricBaseUnit<System, Quantity>>(private val unit: Unit) : MetricMultipleUnit<System, Quantity, Unit> where System : MeasurementSystem, System : MeasurementUsage.UsedInMetric {
    override val symbol: String = "k${unit.symbol}"
    override val system: System = unit.system
    override val quantity: Quantity = unit.quantity
    override fun fromSIUnit(value: Decimal): Decimal = unit.fromSIUnit(value) / 1000.0.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = unit.toSIUnit(value) * 1000.0.toDecimal()
}

class Hecto<System, Quantity : PhysicalQuantity, Unit : MetricBaseUnit<System, Quantity>>(private val unit: Unit) : MetricMultipleUnit<System, Quantity, Unit> where System : MeasurementSystem, System : MeasurementUsage.UsedInMetric {
    override val symbol: String = "h${unit.symbol}"
    override val system: System = unit.system
    override val quantity: Quantity = unit.quantity
    override fun fromSIUnit(value: Decimal): Decimal = unit.fromSIUnit(value) / 100.0.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = unit.toSIUnit(value) * 100.0.toDecimal()
}

class Deca<System, Quantity : PhysicalQuantity, Unit : MetricBaseUnit<System, Quantity>>(private val unit: Unit) : MetricMultipleUnit<System, Quantity, Unit> where System : MeasurementSystem, System : MeasurementUsage.UsedInMetric {
    override val symbol: String = "da${unit.symbol}"
    override val system: System = unit.system
    override val quantity: Quantity = unit.quantity
    override fun fromSIUnit(value: Decimal): Decimal = unit.fromSIUnit(value) / 10.0.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = unit.toSIUnit(value) * 10.0.toDecimal()
}

class Deci<System, Quantity : PhysicalQuantity, Unit : MetricBaseUnit<System, Quantity>>(private val unit: Unit) : MetricMultipleUnit<System, Quantity, Unit> where System : MeasurementSystem, System : MeasurementUsage.UsedInMetric {
    override val symbol: String = "d${unit.symbol}"
    override val system: System = unit.system
    override val quantity: Quantity = unit.quantity
    override fun fromSIUnit(value: Decimal): Decimal = unit.fromSIUnit(value) * 10.0.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = unit.toSIUnit(value) / 10.0.toDecimal()
}

class Centi<System, Quantity : PhysicalQuantity, Unit : MetricBaseUnit<System, Quantity>>(private val unit: Unit) : MetricMultipleUnit<System, Quantity, Unit> where System : MeasurementSystem, System : MeasurementUsage.UsedInMetric {
    override val symbol: String = "c${unit.symbol}"
    override val system: System = unit.system
    override val quantity: Quantity = unit.quantity
    override fun fromSIUnit(value: Decimal): Decimal = unit.fromSIUnit(value) * 100.0.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = unit.toSIUnit(value) / 100.0.toDecimal()
}

class Milli<System, Quantity : PhysicalQuantity, Unit : MetricBaseUnit<System, Quantity>>(private val unit: Unit) : MetricMultipleUnit<System, Quantity, Unit> where System : MeasurementSystem, System : MeasurementUsage.UsedInMetric {
    override val symbol: String = "m${unit.symbol}"
    override val system: System = unit.system
    override val quantity: Quantity = unit.quantity
    override fun fromSIUnit(value: Decimal): Decimal = unit.fromSIUnit(value) * 1000.0.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = unit.toSIUnit(value) / 1000.0.toDecimal()
}

class Micro<System, Quantity : PhysicalQuantity, Unit : MetricBaseUnit<System, Quantity>>(private val unit: Unit) : MetricMultipleUnit<System, Quantity, Unit> where System : MeasurementSystem, System : MeasurementUsage.UsedInMetric {
    override val symbol: String = "μ${unit.symbol}"
    override val system: System = unit.system
    override val quantity: Quantity = unit.quantity
    override fun fromSIUnit(value: Decimal): Decimal = unit.fromSIUnit(value) * 1000000.0.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = unit.toSIUnit(value) / 1000000.0.toDecimal()
}

class Nano<System, Quantity : PhysicalQuantity, Unit : MetricBaseUnit<System, Quantity>>(private val unit: Unit) : MetricMultipleUnit<System, Quantity, Unit> where System : MeasurementSystem, System : MeasurementUsage.UsedInMetric {
    override val symbol: String = "n${unit.symbol}"
    override val system: System = unit.system
    override val quantity: Quantity = unit.quantity
    override fun fromSIUnit(value: Decimal): Decimal = unit.fromSIUnit(value) * 1000000000.0.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = unit.toSIUnit(value) / 1000000000.0.toDecimal()
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricMolalityUnits: Set<MetricMolality> get() = AmountOfSubstanceUnits.flatMap { amountOfSubstance ->
    MetricWeightUnits.map { amountOfSubstance per it }
}.toSet()

val ImperialMolalityUnits: Set<ImperialMolality> get() = AmountOfSubstanceUnits.flatMap { amountOfSubstance ->
    ImperialWeightUnits.map { amountOfSubstance per it }
}.toSet()

val UKImperialMolalityUnits: Set<UKImperialMolality> get() = AmountOfSubstanceUnits.flatMap { amountOfSubstance ->
    UKImperialWeightUnits.map { amountOfSubstance per it }
}.toSet()

val USCustomaryMolalityUnits: Set<USCustomaryMolality> get() = AmountOfSubstanceUnits.flatMap { amountOfSubstance ->
    USCustomaryWeightUnits.map { amountOfSubstance per it }
}.toSet()

val MolalityUnits: Set<Molality> get() = MetricMolalityUnits +
    ImperialMolalityUnits +
    UKImperialMolalityUnits.filter { it.per !is UKImperialImperialWeightWrapper }.toSet() +
    USCustomaryMolalityUnits.filter { it.per !is USCustomaryImperialWeightWrapper }.toSet()

@Serializable
sealed class Molality : AbstractScientificUnit<PhysicalQuantity.Molality>() {
    abstract val amountOfSubstance: AmountOfSubstance
    abstract val per: Weight
    override val symbol: String by lazy { "${amountOfSubstance.symbol} / ${per.symbol}" }
    override val quantity = PhysicalQuantity.Molality
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(amountOfSubstance.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = amountOfSubstance.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricMolality(override val amountOfSubstance: AmountOfSubstance, override val per: MetricWeight) : Molality(), MetricScientificUnit<PhysicalQuantity.Molality> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialMolality(override val amountOfSubstance: AmountOfSubstance, override val per: ImperialWeight) : Molality(), ImperialScientificUnit<PhysicalQuantity.Molality> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = amountOfSubstance per per.ukImperial
    val usCustomary get() = amountOfSubstance per per.usCustomary
}
@Serializable
data class USCustomaryMolality(override val amountOfSubstance: AmountOfSubstance, override val per: USCustomaryWeight) : Molality(), USCustomaryScientificUnit<PhysicalQuantity.Molality> {
    override val system = MeasurementSystem.USCustomary
}
@Serializable
data class UKImperialMolality(override val amountOfSubstance: AmountOfSubstance, override val per: UKImperialWeight) : Molality(), UKImperialScientificUnit<PhysicalQuantity.Molality> {
    override val system = MeasurementSystem.UKImperial
}

infix fun AmountOfSubstance.per(weight: MetricWeight) = MetricMolality(this, weight)
infix fun AmountOfSubstance.per(weight: ImperialWeight) = ImperialMolality(this, weight)
infix fun AmountOfSubstance.per(weight: UKImperialWeight) = UKImperialMolality(this, weight)
infix fun AmountOfSubstance.per(weight: USCustomaryWeight) = USCustomaryMolality(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricAndImperialMolarEnergyUnits: Set<MetricAndImperialMolarEnergy> get() = MetricAndImperialEnergyUnits.flatMap { energy ->
    AmountOfSubstanceUnits.map { energy per it }
}.toSet()

val MetricMolarEnergyUnits: Set<MetricMolarEnergy> get() = MetricEnergyUnits.flatMap { energy ->
    AmountOfSubstanceUnits.map { energy per it }
}.toSet()

val ImperialMolarEnergyUnits: Set<ImperialMolarEnergy> get() = ImperialEnergyUnits.flatMap { energy ->
    AmountOfSubstanceUnits.map { energy per it }
}.toSet()

val MolarEnergyUnits: Set<MolarEnergy> get() = MetricAndImperialMolarEnergyUnits +
    MetricMolarEnergyUnits.filter { it.energy !is MetricMetricAndImperialEnergyWrapper }.toSet() +
    ImperialMolarEnergyUnits.filter { it.energy !is ImperialMetricAndImperialEnergyWrapper }.toSet()

@Serializable
sealed class MolarEnergy : AbstractScientificUnit<PhysicalQuantity.MolarEnergy>() {
    abstract val energy: Energy
    abstract val per: AmountOfSubstance
    override val quantity = PhysicalQuantity.MolarEnergy
    override val symbol: String by lazy { "${energy.symbol}/${per.symbol}" }
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(energy.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = energy.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricAndImperialMolarEnergy(override val energy: MetricAndImperialEnergy, override val per: AmountOfSubstance) : MolarEnergy(), MetricAndImperialScientificUnit<PhysicalQuantity.MolarEnergy> {
    override val system = MeasurementSystem.MetricAndImperial
    val metric get() = energy.metric per per
    val imperial get() = energy.imperial per per
}
@Serializable
data class MetricMolarEnergy(override val energy: MetricEnergy, override val per: AmountOfSubstance) : MolarEnergy(), MetricScientificUnit<PhysicalQuantity.MolarEnergy> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialMolarEnergy(override val energy: ImperialEnergy, override val per: AmountOfSubstance) : MolarEnergy(), ImperialScientificUnit<PhysicalQuantity.MolarEnergy> {
    override val system = MeasurementSystem.Imperial
}

infix fun MetricAndImperialEnergy.per(amountOfSubstance: AmountOfSubstance) = MetricAndImperialMolarEnergy(this, amountOfSubstance)
infix fun MetricEnergy.per(amountOfSubstance: AmountOfSubstance) = MetricMolarEnergy(this, amountOfSubstance)
infix fun ImperialEnergy.per(amountOfSubstance: AmountOfSubstance) = ImperialMolarEnergy(this, amountOfSubstance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricMolarMassUnits: Set<MetricMolarMass> get() = MetricWeightUnits.flatMap { weight ->
    AmountOfSubstanceUnits.map { weight per it }
}.toSet()

val ImperialMolarMassUnits: Set<ImperialMolarMass> get() = ImperialWeightUnits.flatMap { weight ->
    AmountOfSubstanceUnits.map { weight per it }
}.toSet()

val UKImperialMolarMassUnits: Set<UKImperialMolarMass> get() = UKImperialWeightUnits.flatMap { weight ->
    AmountOfSubstanceUnits.map { weight per it }
}.toSet()

val USCustomaryMolarMassUnits: Set<USCustomaryMolarMass> get() = USCustomaryWeightUnits.flatMap { weight ->
    AmountOfSubstanceUnits.map { weight per it }
}.toSet()

val MolarMassUnits: Set<MolarMass> get() = MetricMolarMassUnits +
    ImperialMolarMassUnits +
    UKImperialMolarMassUnits.filter { it.weight !is UKImperialImperialWeightWrapper }.toSet() +
    USCustomaryMolarMassUnits.filter { it.weight !is USCustomaryImperialWeightWrapper }.toSet()

@Serializable
sealed class MolarMass : AbstractScientificUnit<PhysicalQuantity.MolarMass>() {
    abstract val weight: Weight
    abstract val per: AmountOfSubstance
    override val symbol: String by lazy { "${weight.symbol} / ${per.symbol}" }
    override val quantity = PhysicalQuantity.MolarMass
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(weight.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = weight.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricMolarMass(override val weight: MetricWeight, override val per: AmountOfSubstance) : MolarMass(), MetricScientificUnit<PhysicalQuantity.MolarMass> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialMolarMass(override val weight: ImperialWeight, override val per: AmountOfSubstance) : MolarMass(), ImperialScientificUnit<PhysicalQuantity.MolarMass> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = weight.ukImperial per per
    val usCustomary get() = weight.usCustomary per per
}
@Serializable
data class USCustomaryMolarMass(override val weight: USCustomaryWeight, override val per: AmountOfSubstance) : MolarMass(), USCustomaryScientificUnit<PhysicalQuantity.MolarMass> {
    override val system = MeasurementSystem.USCustomary
}
@Serializable
data class UKImperialMolarMass(override val weight: UKImperialWeight, override val per: AmountOfSubstance) : MolarMass(), UKImperialScientificUnit<PhysicalQuantity.MolarMass> {
    override val system = MeasurementSystem.UKImperial
}

infix fun MetricWeight.per(amountOfSubstance: AmountOfSubstance) = MetricMolarMass(this, amountOfSubstance)
infix fun ImperialWeight.per(amountOfSubstance: AmountOfSubstance) = ImperialMolarMass(this, amountOfSubstance)
infix fun USCustomaryWeight.per(amountOfSubstance: AmountOfSubstance) = USCustomaryMolarMass(this, amountOfSubstance)
infix fun UKImperialWeight.per(amountOfSubstance: AmountOfSubstance) = UKImperialMolarMass(this, amountOfSubstance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricMolarVolumeUnits: Set<MetricMolarVolume> get() = MetricVolumeUnits.flatMap { volume ->
    AmountOfSubstanceUnits.map { volume per it }
}.toSet()

val ImperialMolarVolumeUnits: Set<ImperialMolarVolume> get() = ImperialVolumeUnits.flatMap { volume ->
    AmountOfSubstanceUnits.map { volume per it }
}.toSet()

val UKImperialMolarVolumeUnits: Set<UKImperialMolarVolume> get() = UKImperialVolumeUnits.flatMap { volume ->
    AmountOfSubstanceUnits.map { volume per it }
}.toSet()

val USCustomaryMolarVolumeUnits: Set<USCustomaryMolarVolume> get() = USCustomaryVolumeUnits.flatMap { volume ->
    AmountOfSubstanceUnits.map { volume per it }
}.toSet()

val MolarVolumeUnits: Set<MolarVolume> get() = MetricMolarVolumeUnits +
    ImperialMolarVolumeUnits +
    UKImperialMolarVolumeUnits.filter { it.volume !is UKImperialImperialVolumeWrapper }.toSet() +
    USCustomaryMolarVolumeUnits.filter { it.volume !is USCustomaryImperialVolumeWrapper }.toSet()

@Serializable
sealed class MolarVolume : AbstractScientificUnit<PhysicalQuantity.MolarVolume>() {
    abstract val volume: Volume
    abstract val per: AmountOfSubstance
    override val symbol: String by lazy { "${volume.symbol} / ${per.symbol}" }
    override val quantity = PhysicalQuantity.MolarVolume
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(volume.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = volume.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricMolarVolume(override val volume: MetricVolume, override val per: AmountOfSubstance) : MolarVolume(), MetricScientificUnit<PhysicalQuantity.MolarVolume> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialMolarVolume(override val volume: ImperialVolume, override val per: AmountOfSubstance) : MolarVolume(), ImperialScientificUnit<PhysicalQuantity.MolarVolume> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = volume.ukImperial per per
    val usCustomary get() = volume.usCustomary per per
}
@Serializable
data class USCustomaryMolarVolume(override val volume: USCustomaryVolume, override val per: AmountOfSubstance) : MolarVolume(), USCustomaryScientificUnit<PhysicalQuantity.MolarVolume> {
    override val system = MeasurementSystem.USCustomary
}
@Serializable
data class UKImperialMolarVolume(override val volume: UKImperialVolume, override val per: AmountOfSubstance) : MolarVolume(), UKImperialScientificUnit<PhysicalQuantity.MolarVolume> {
    override val system = MeasurementSystem.UKImperial
}

infix fun MetricVolume.per(amountOfSubstance: AmountOfSubstance) = MetricMolarVolume(this, amountOfSubstance)
infix fun ImperialVolume.per(amountOfSubstance: AmountOfSubstance) = ImperialMolarVolume(this, amountOfSubstance)
infix fun USCustomaryVolume.per(amountOfSubstance: AmountOfSubstance) = USCustomaryMolarVolume(this, amountOfSubstance)
infix fun UKImperialVolume.per(amountOfSubstance: AmountOfSubstance) = UKImperialMolarVolume(this, amountOfSubstance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricMolarityUnits: Set<MetricMolarity> get() = AmountOfSubstanceUnits.flatMap { amountOfSubstance ->
    MetricVolumeUnits.map { amountOfSubstance per it }
}.toSet()

val ImperialMolarityUnits: Set<ImperialMolarity> get() = AmountOfSubstanceUnits.flatMap { amountOfSubstance ->
    ImperialVolumeUnits.map { amountOfSubstance per it }
}.toSet()

val UKImperialMolarityUnits: Set<UKImperialMolarity> get() = AmountOfSubstanceUnits.flatMap { amountOfSubstance ->
    UKImperialVolumeUnits.map { amountOfSubstance per it }
}.toSet()

val USCustomaryMolarityUnits: Set<USCustomaryMolarity> get() = AmountOfSubstanceUnits.flatMap { amountOfSubstance ->
    USCustomaryVolumeUnits.map { amountOfSubstance per it }
}.toSet()

val MolarityUnits: Set<Molarity> get() = MetricMolarityUnits +
    ImperialMolarityUnits +
    UKImperialMolarityUnits.filter { it.per !is UKImperialImperialVolumeWrapper }.toSet() +
    USCustomaryMolarityUnits.filter { it.per !is USCustomaryImperialVolumeWrapper }.toSet()

@Serializable
sealed class Molarity : AbstractScientificUnit<PhysicalQuantity.Molarity>() {
    abstract val amountOfSubstance: AmountOfSubstance
    abstract val per: Volume
    override val symbol: String by lazy { "${amountOfSubstance.symbol} / ${per.symbol}" }
    override val quantity = PhysicalQuantity.Molarity
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(amountOfSubstance.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = amountOfSubstance.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricMolarity(override val amountOfSubstance: AmountOfSubstance, override val per: MetricVolume) : Molarity(), MetricScientificUnit<PhysicalQuantity.Molarity> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialMolarity(override val amountOfSubstance: AmountOfSubstance, override val per: ImperialVolume) : Molarity(), ImperialScientificUnit<PhysicalQuantity.Molarity> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = amountOfSubstance per per.ukImperial
    val usCustomary get() = amountOfSubstance per per.usCustomary
}
@Serializable
data class USCustomaryMolarity(override val amountOfSubstance: AmountOfSubstance, override val per: USCustomaryVolume) : Molarity(), USCustomaryScientificUnit<PhysicalQuantity.Molarity> {
    override val system = MeasurementSystem.USCustomary
}
@Serializable
data class UKImperialMolarity(override val amountOfSubstance: AmountOfSubstance, override val per: UKImperialVolume) : Molarity(), UKImperialScientificUnit<PhysicalQuantity.Molarity> {
    override val system = MeasurementSystem.UKImperial
}

infix fun AmountOfSubstance.per(volume: MetricVolume) = MetricMolarity(this, volume)
infix fun AmountOfSubstance.per(volume: ImperialVolume) = ImperialMolarity(this, volume)
infix fun AmountOfSubstance.per(volume: USCustomaryVolume) = USCustomaryMolarity(this, volume)
infix fun AmountOfSubstance.per(volume: UKImperialVolume) = UKImperialMolarity(this, volume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricMomentumUnits: Set<MetricMomentum> get() = MetricWeightUnits.flatMap { weight ->
    MetricSpeedUnits.map {
        MetricMomentum(weight, it)
    }
}.toSet()

val ImperialMomentumUnits: Set<ImperialMomentum> get() = ImperialWeightUnits.flatMap { weight ->
    ImperialSpeedUnits.map {
        ImperialMomentum(weight, it)
    }
}.toSet()

val UKImperialMomentumUnits: Set<UKImperialMomentum> get() = UKImperialWeightUnits.flatMap { weight ->
    ImperialSpeedUnits.map {
        UKImperialMomentum(weight, it)
    }
}.toSet()

val USCustomaryMomentumUnits: Set<USCustomaryMomentum> get() = USCustomaryWeightUnits.flatMap { weight ->
    ImperialSpeedUnits.map {
        USCustomaryMomentum(weight, it)
    }
}.toSet()

val MomentumUnits: Set<Momentum> get() = MetricMomentumUnits +
    ImperialMomentumUnits +
    UKImperialMomentumUnits.filter { it.mass !is UKImperialImperialWeightWrapper }.toSet() +
    USCustomaryMomentumUnits.filter { it.mass !is USCustomaryImperialWeightWrapper }.toSet()

@Serializable
sealed class Momentum : AbstractScientificUnit<PhysicalQuantity.Momentum>() {
    abstract val mass: Weight
    abstract val speed: Speed
    override val quantity = PhysicalQuantity.Momentum
    override val symbol: String by lazy { "${mass.symbol}⋅${speed.symbol}" }
    override fun fromSIUnit(value: Decimal): Decimal = speed.fromSIUnit(mass.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = mass.toSIUnit(speed.toSIUnit(value))
}

@Serializable
data class MetricMomentum(override val mass: MetricWeight, override val speed: MetricSpeed) : Momentum(), MetricScientificUnit<PhysicalQuantity.Momentum> {
    override val system = MeasurementSystem.Metric
}

@Serializable
data class ImperialMomentum(override val mass: ImperialWeight, override val speed: ImperialSpeed) : Momentum(), ImperialScientificUnit<PhysicalQuantity.Momentum> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = UKImperialMomentum(mass.ukImperial, speed)
    val usCustomary get() = USCustomaryMomentum(mass.usCustomary, speed)
}

@Serializable
data class UKImperialMomentum(override val mass: UKImperialWeight, override val speed: ImperialSpeed) : Momentum(), UKImperialScientificUnit<PhysicalQuantity.Momentum> {
    override val system = MeasurementSystem.UKImperial
}

@Serializable
data class USCustomaryMomentum(override val mass: USCustomaryWeight, override val speed: ImperialSpeed) : Momentum(), USCustomaryScientificUnit<PhysicalQuantity.Momentum> {
    override val system = MeasurementSystem.USCustomary
}

infix fun MetricWeight.x(speed: MetricSpeed) = MetricMomentum(this, speed)
infix fun ImperialWeight.x(speed: ImperialSpeed) = ImperialMomentum(this, speed)
infix fun UKImperialWeight.x(speed: ImperialSpeed) = UKImperialMomentum(this, speed)
infix fun USCustomaryWeight.x(speed: ImperialSpeed) = USCustomaryMomentum(this, speed)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricAndImperialPowerUnits: Set<MetricAndImperialPower> get() = setOf(
    Watt,
    Nanowatt,
    Microwatt,
    Milliwatt,
    Centiwatt,
    Deciwatt,
    Decawatt,
    Hectowatt,
    Kilowatt,
    Megawatt,
    Gigawatt
)

val MetricPowerUnits: Set<MetricPower> get() = MetricAndImperialPowerUnits.map { it.metric }.toSet() +
    setOf(
        ErgPerSecond,
        MetricHorsepower
    )
val ImperialPowerUnits: Set<ImperialPower> get() = MetricAndImperialPowerUnits.map { it.imperial }.toSet() +
    setOf(
        FootPoundForcePerSecond,
        FootPoundForcePerMinute,
        InchPoundForcePerSecond,
        InchPoundForcePerMinute,
        Horsepower,
        BritishThermalUnitPerSecond,
        BritishThermalUnitPerMinute,
        BritishThermalUnitPerHour
    )

val PowerUnits: Set<Power> get() = MetricAndImperialPowerUnits +
    MetricPowerUnits.filter { it !is MetricMetricAndImperialPowerWrapper }.toSet() +
    ImperialPowerUnits.filter { it !is ImperialMetricAndImperialPowerWrapper }.toSet()

@Serializable
sealed class Power : AbstractScientificUnit<PhysicalQuantity.Power>()

@Serializable
sealed class MetricAndImperialPower : Power(), MetricAndImperialScientificUnit<PhysicalQuantity.Power>
@Serializable
sealed class MetricPower : Power(), MetricScientificUnit<PhysicalQuantity.Power>
@Serializable
sealed class ImperialPower : Power(), ImperialScientificUnit<PhysicalQuantity.Power>

@Serializable
object Watt : MetricAndImperialPower(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Power> {
    override val symbol: String = "W"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Power
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}
@Serializable
object Nanowatt : MetricAndImperialPower(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Power, Watt> by Nano(Watt)
@Serializable
object Microwatt : MetricAndImperialPower(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Power, Watt> by Micro(Watt)
@Serializable
object Milliwatt : MetricAndImperialPower(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Power, Watt> by Milli(Watt)
@Serializable
object Centiwatt : MetricAndImperialPower(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Power, Watt> by Centi(Watt)
@Serializable
object Deciwatt : MetricAndImperialPower(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Power, Watt> by Deci(Watt)
@Serializable
object Decawatt : MetricAndImperialPower(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Power, Watt> by Deca(Watt)
@Serializable
object Hectowatt : MetricAndImperialPower(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Power, Watt> by Hecto(Watt)
@Serializable
object Kilowatt : MetricAndImperialPower(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Power, Watt> by Kilo(Watt)
@Serializable
object Megawatt : MetricAndImperialPower(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Power, Watt> by Mega(Watt)
@Serializable
object Gigawatt : MetricAndImperialPower(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Power, Watt> by Giga(Watt)

@Serializable
object ErgPerSecond : MetricPower(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Power> {
    override val symbol: String = "erg/s"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Power
    override fun fromSIUnit(value: Decimal): Decimal = Erg.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = Erg.toSIUnit(value)
}

@Serializable
object MetricHorsepower : MetricPower() {
    private const val KILOGRAM_FORCE_METER_SECOND_TO_WATT = 75.0
    override val symbol: String = "PS"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Power
    override fun fromSIUnit(value: Decimal): Decimal = KilogramForce.fromSIUnit(value) / KILOGRAM_FORCE_METER_SECOND_TO_WATT.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = KilogramForce.toSIUnit(value * KILOGRAM_FORCE_METER_SECOND_TO_WATT.toDecimal())
}

@Serializable
object Horsepower : ImperialPower() {
    private const val FOOTPOUND_PER_MINUTE = 33000
    override val symbol: String = "hp"
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Power
    override fun fromSIUnit(value: Decimal): Decimal = FootPoundForce.fromSIUnit(Minute.toSIUnit(value)) / FOOTPOUND_PER_MINUTE.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = Minute.fromSIUnit(FootPoundForce.toSIUnit(value * FOOTPOUND_PER_MINUTE.toDecimal()))
}

@Serializable
object FootPoundForcePerSecond : ImperialPower() {
    override val symbol: String = "${FootPoundForce.symbol} / ${Second.symbol}"
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Power
    override fun fromSIUnit(value: Decimal): Decimal = FootPoundForce.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = FootPoundForce.toSIUnit(value)
}

@Serializable
object FootPoundForcePerMinute : ImperialPower() {
    override val symbol: String = "${FootPoundForce.symbol} / ${Minute.symbol}"
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Power
    override fun fromSIUnit(value: Decimal): Decimal = Minute.toSIUnit(FootPoundForce.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = FootPoundForce.toSIUnit(Minute.fromSIUnit(value))
}

@Serializable
object InchPoundForcePerSecond : ImperialPower() {
    override val symbol: String = "${FootPoundForce.symbol} / ${Second.symbol}"
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Power
    override fun fromSIUnit(value: Decimal): Decimal = InchPoundForce.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = InchPoundForce.toSIUnit(value)
}

@Serializable
object InchPoundForcePerMinute : ImperialPower() {
    override val symbol: String = "${FootPoundForce.symbol} / ${Minute.symbol}"
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Power
    override fun fromSIUnit(value: Decimal): Decimal = Minute.toSIUnit(InchPoundForce.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = InchPoundForce.toSIUnit(Minute.fromSIUnit(value))
}

@Serializable
object BritishThermalUnitPerSecond : ImperialPower() {
    override val symbol: String = "${BritishThermalUnit.symbol} / ${Second.symbol}"
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Power
    override fun fromSIUnit(value: Decimal): Decimal = BritishThermalUnit.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = BritishThermalUnit.toSIUnit(value)
}

@Serializable
object BritishThermalUnitPerMinute : ImperialPower() {
    override val symbol: String = "${BritishThermalUnit.symbol} / ${Minute.symbol}"
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Power
    override fun fromSIUnit(value: Decimal): Decimal = Minute.toSIUnit(BritishThermalUnit.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = BritishThermalUnit.toSIUnit(Minute.fromSIUnit(value))
}

@Serializable
object BritishThermalUnitPerHour : ImperialPower() {
    override val symbol: String = "${BritishThermalUnit.symbol} / ${Hour.symbol}"
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Power
    override fun fromSIUnit(value: Decimal): Decimal = Hour.toSIUnit(BritishThermalUnit.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = BritishThermalUnit.toSIUnit(Hour.fromSIUnit(value))
}
@Serializable
data class MetricMetricAndImperialPowerWrapper(val metricAndImperialPower: MetricAndImperialPower) : MetricPower() {
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Power
    override val symbol: String = metricAndImperialPower.symbol
    override fun fromSIUnit(value: Decimal): Decimal = metricAndImperialPower.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = metricAndImperialPower.toSIUnit(value)
}

val <PowerUnit : MetricAndImperialPower> PowerUnit.metric get() = MetricMetricAndImperialPowerWrapper(this)

@Serializable
data class ImperialMetricAndImperialPowerWrapper(val metricAndImperialPower: MetricAndImperialPower) : ImperialPower() {
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Power
    override val symbol: String = metricAndImperialPower.symbol
    override fun fromSIUnit(value: Decimal): Decimal = metricAndImperialPower.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = metricAndImperialPower.toSIUnit(value)
}

val <PowerUnit : MetricAndImperialPower> PowerUnit.imperial get() = ImperialMetricAndImperialPowerWrapper(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.convertValue
import com.splendo.kaluga.scientific.invoke
import kotlinx.serialization.Serializable

val MetricPressureUnits: Set<MetricPressure> get() = setOf(
    Pascal,
    Nanopascal,
    Micropascal,
    Millipascal,
    Centipascal,
    Decipascal,
    Decapascal,
    Hectopascal,
    Kilopascal,
    Megapascal,
    Gigapascal,
    Bar,
    Nanobar,
    Microbar,
    Millibar,
    Centibar,
    Decibar,
    Decabar,
    Hectobar,
    Kilobar,
    Megabar,
    Gigabar,
    Barye,
    Nanobarye,
    Microbarye,
    Millibarye,
    Centibarye,
    Decibarye,
    Decabarye,
    Hectobarye,
    Kilobarye,
    Megabarye,
    Gigabarye,
    Atmosphere,
    Torr,
    Nanotorr,
    Microtorr,
    Millitorr,
    Centitorr,
    Decitorr,
    Decatorr,
    Hectotorr,
    Kilotorr,
    Megatorr,
    Gigatorr,
    MillimeterOfMercury,
    MillimeterOfWater,
    CentimeterOfWater
)

val ImperialPressureUnits: Set<ImperialPressure> get() = setOf(
    PoundSquareInch,
    PoundSquareFoot,
    OunceSquareInch,
    KiloPoundSquareInch,
    InchOfMercury,
    InchOfWater,
    FootOfWater
)

val USCustomaryPressureUnits: Set<USCustomaryPressure> get() = setOf(
    KipSquareInch,
    KipSquareFoot,
    USTonSquareInch,
    USTonSquareFoot
) + ImperialPressureUnits.map { it.usCustomary }.toSet()

val UKImperialPressureUnits: Set<UKImperialPressure> get() = setOf(
    ImperialTonSquareInch,
    ImperialTonSquareFoot
) + ImperialPressureUnits.map { it.ukImperial }.toSet()

val PressureUnits: Set<Pressure> get() = MetricPressureUnits +
    ImperialPressureUnits +
    USCustomaryPressureUnits.filter { it !is USCustomaryImperialPressureWrapper }.toSet() +
    UKImperialPressureUnits.filter { it !is UKImperialPressureWrapper }.toSet()

@Serializable
sealed class Pressure : AbstractScientificUnit<PhysicalQuantity.Pressure>()

@Serializable
sealed class MetricPressure : Pressure(), MetricScientificUnit<PhysicalQuantity.Pressure>

@Serializable
sealed class ImperialPressure : Pressure(), ImperialScientificUnit<PhysicalQuantity.Pressure> {
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Pressure
}

@Serializable
sealed class UKImperialPressure : Pressure(), UKImperialScientificUnit<PhysicalQuantity.Pressure> {
    override val system = MeasurementSystem.UKImperial
    override val quantity = PhysicalQuantity.Pressure
}

@Serializable
sealed class USCustomaryPressure : Pressure(), USCustomaryScientificUnit<PhysicalQuantity.Pressure> {
    override val system = MeasurementSystem.USCustomary
    override val quantity = PhysicalQuantity.Pressure
}

@Serializable
object Pascal : MetricPressure(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure> {
    override val symbol: String = "P"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Pressure
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}
@Serializable
object Nanopascal : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Pascal> by Nano(Pascal)
@Serializable
object Micropascal : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Pascal> by Micro(Pascal)
@Serializable
object Millipascal : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Pascal> by Milli(Pascal)
@Serializable
object Centipascal : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Pascal> by Centi(Pascal)
@Serializable
object Decipascal : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Pascal> by Deci(Pascal)
@Serializable
object Decapascal : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Pascal> by Deca(Pascal)
@Serializable
object Hectopascal : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Pascal> by Hecto(Pascal)
@Serializable
object Kilopascal : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Pascal> by Kilo(Pascal)
@Serializable
object Megapascal : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Pascal> by Mega(Pascal)
@Serializable
object Gigapascal : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Pascal> by Giga(Pascal)
@Serializable
object Bar : MetricPressure(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure> {
    private const val BAR_PER_PASCAL = 0.00001
    override val symbol: String = "bar"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Pressure
    override fun fromSIUnit(value: Decimal): Decimal = value * BAR_PER_PASCAL.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value / BAR_PER_PASCAL.toDecimal()
}

@Serializable
object Nanobar : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Bar> by Nano(Bar)
@Serializable
object Microbar : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Bar> by Micro(Bar)
@Serializable
object Millibar : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Bar> by Milli(Bar)
@Serializable
object Centibar : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Bar> by Centi(Bar)
@Serializable
object Decibar : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Bar> by Deci(Bar)
@Serializable
object Decabar : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Bar> by Deca(Bar)
@Serializable
object Hectobar : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Bar> by Hecto(Bar)
@Serializable
object Kilobar : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Bar> by Kilo(Bar)
@Serializable
object Megabar : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Bar> by Mega(Bar)
@Serializable
object Gigabar : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Bar> by Giga(Bar)

@Serializable
object Barye : MetricPressure(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure> {
    private const val BARYE_PER_PASCAL = 10
    override val symbol: String = "Ba"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Pressure
    override fun fromSIUnit(value: Decimal): Decimal = value * BARYE_PER_PASCAL.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value / BARYE_PER_PASCAL.toDecimal()
}

@Serializable
object Nanobarye : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> by Nano(Barye)
@Serializable
object Microbarye : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> by Micro(Barye)
@Serializable
object Millibarye : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> by Milli(Barye)
@Serializable
object Centibarye : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> by Centi(Barye)
@Serializable
object Decibarye : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> by Deci(Barye)
@Serializable
object Decabarye : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> by Deca(Barye)
@Serializable
object Hectobarye : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> by Hecto(Barye)
@Serializable
object Kilobarye : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> by Kilo(Barye)
@Serializable
object Megabarye : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> by Mega(Barye)
@Serializable
object Gigabarye : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> by Giga(Barye)

@Serializable
object Atmosphere : MetricPressure() {
    private const val PASCAL_PER_ATMOSPHERE = 101325
    override val symbol: String = "atm"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Pressure
    override fun fromSIUnit(value: Decimal): Decimal = value / PASCAL_PER_ATMOSPHERE.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value * PASCAL_PER_ATMOSPHERE.toDecimal()
}
@Serializable
object Torr : MetricPressure(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure> {
    private const val TORR_PER_ATMOSPHERE = 760
    override val symbol: String = "Torr"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Pressure
    override fun fromSIUnit(value: Decimal): Decimal = Atmosphere.fromSIUnit(value) * TORR_PER_ATMOSPHERE.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = Atmosphere.toSIUnit(value / TORR_PER_ATMOSPHERE.toDecimal())
}

@Serializable
object Nanotorr : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Torr> by Nano(Torr)
@Serializable
object Microtorr : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Torr> by Micro(Torr)
@Serializable
object Millitorr : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Torr> by Milli(Torr)
@Serializable
object Centitorr : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Torr> by Centi(Torr)
@Serializable
object Decitorr : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Torr> by Deci(Torr)
@Serializable
object Decatorr : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Torr> by Deca(Torr)
@Serializable
object Hectotorr : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Torr> by Hecto(Torr)
@Serializable
object Kilotorr : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Torr> by Kilo(Torr)
@Serializable
object Megatorr : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Torr> by Mega(Torr)
@Serializable
object Gigatorr : MetricPressure(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Torr> by Giga(Torr)

@Serializable
object MillimeterOfMercury : MetricPressure() {
    private const val PASCAL_PER_MMHG = 133.322387415
    override val symbol: String = "mmHg"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Pressure
    override fun fromSIUnit(value: Decimal): Decimal = value / PASCAL_PER_MMHG.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value * PASCAL_PER_MMHG.toDecimal()
}
@Serializable
object CentimeterOfWater : MetricPressure() {
    override val symbol: String = "cmH2O"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Pressure
    override fun fromSIUnit(value: Decimal): Decimal = MillimeterOfWater.fromSIUnit(value) / 10.0.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = MillimeterOfWater.toSIUnit(value * 10.0.toDecimal())
}
@Serializable
object MillimeterOfWater : MetricPressure() {
    private const val PASCAL_PER_MMH2O = 9.80665
    override val symbol: String = "mmH2O"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Pressure
    override fun fromSIUnit(value: Decimal): Decimal = value / PASCAL_PER_MMH2O.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value * PASCAL_PER_MMH2O.toDecimal()
}

@Serializable
object PoundSquareInch : ImperialPressure() {
    override val symbol: String = "psi"
    override fun fromSIUnit(value: Decimal): Decimal = PoundForce.fromSIUnit(value) / SquareInch.fromSIUnit(1.toDecimal())
    override fun toSIUnit(value: Decimal): Decimal = PoundForce.toSIUnit(value * SquareInch.fromSIUnit(1.toDecimal()))
}

@Serializable
object PoundSquareFoot : ImperialPressure() {
    override val symbol: String = "${PoundForce.symbol}/${SquareFoot.symbol}"
    override fun fromSIUnit(value: Decimal): Decimal = PoundForce.fromSIUnit(value) / SquareFoot.fromSIUnit(1.toDecimal())
    override fun toSIUnit(value: Decimal): Decimal = PoundForce.toSIUnit(value * SquareFoot.fromSIUnit(1.toDecimal()))
}

@Serializable
object OunceSquareInch : ImperialPressure() {
    override val symbol: String = "${OunceForce.symbol}/${SquareInch.symbol}"
    override fun fromSIUnit(value: Decimal): Decimal = OunceForce.fromSIUnit(value) / SquareInch.fromSIUnit(1.toDecimal())
    override fun toSIUnit(value: Decimal): Decimal = OunceForce.toSIUnit(value * SquareInch.fromSIUnit(1.toDecimal()))
}

@Serializable
object KiloPoundSquareInch : ImperialPressure() {
    private const val POUND_PER_KILOPOUND_SQUARE_INCH = 1000.0
    override val symbol: String = "ksi"
    override fun fromSIUnit(value: Decimal): Decimal = PoundSquareInch.fromSIUnit(value) / POUND_PER_KILOPOUND_SQUARE_INCH.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = PoundSquareInch.toSIUnit(value * POUND_PER_KILOPOUND_SQUARE_INCH.toDecimal())
}
@Serializable
object InchOfMercury : ImperialPressure() {
    override val symbol: String = "inHg"
    override fun fromSIUnit(value: Decimal): Decimal = MillimeterOfMercury.fromSIUnit(value) * 1(Millimeter).convertValue(Inch)
    override fun toSIUnit(value: Decimal): Decimal = MillimeterOfMercury.toSIUnit(value / 1(Millimeter).convertValue(Inch))
}
@Serializable
object InchOfWater : ImperialPressure() {
    override val symbol: String = "inH2O"
    override fun fromSIUnit(value: Decimal): Decimal = MillimeterOfWater.fromSIUnit(value) * 1(Millimeter).convertValue(Inch)
    override fun toSIUnit(value: Decimal): Decimal = MillimeterOfWater.toSIUnit(value / 1(Millimeter).convertValue(Inch))
}
@Serializable
object FootOfWater : ImperialPressure() {
    override val symbol: String = "ftH2O"
    override fun fromSIUnit(value: Decimal): Decimal = MillimeterOfWater.fromSIUnit(value) * 1(Millimeter).convertValue(Foot)
    override fun toSIUnit(value: Decimal): Decimal = MillimeterOfWater.toSIUnit(value / 1(Millimeter).convertValue(Foot))
}
@Serializable
object KipSquareInch : USCustomaryPressure() {
    override val symbol: String = "${Kip.symbol}/${SquareInch.symbol}"
    override fun fromSIUnit(value: Decimal): Decimal = Kip.fromSIUnit(value) / SquareInch.fromSIUnit(1.toDecimal())
    override fun toSIUnit(value: Decimal): Decimal = Kip.toSIUnit(value * SquareInch.fromSIUnit(1.toDecimal()))
}

@Serializable
object KipSquareFoot : USCustomaryPressure() {
    override val symbol: String = "${Kip.symbol}/${SquareFoot.symbol}"
    override fun fromSIUnit(value: Decimal): Decimal = Kip.fromSIUnit(value) / SquareFoot.fromSIUnit(1.toDecimal())
    override fun toSIUnit(value: Decimal): Decimal = Kip.toSIUnit(value * SquareFoot.fromSIUnit(1.toDecimal()))
}
@Serializable
object USTonSquareInch : USCustomaryPressure() {
    override val symbol: String = "${UsTonForce.symbol}/${SquareInch.symbol}"
    override fun fromSIUnit(value: Decimal): Decimal = UsTonForce.fromSIUnit(value) / SquareInch.fromSIUnit(1.toDecimal())
    override fun toSIUnit(value: Decimal): Decimal = UsTonForce.toSIUnit(value * SquareInch.fromSIUnit(1.toDecimal()))
}

@Serializable
object USTonSquareFoot : USCustomaryPressure() {
    override val symbol: String = "${UsTonForce.symbol}/${SquareFoot.symbol}"
    override fun fromSIUnit(value: Decimal): Decimal = UsTonForce.fromSIUnit(value) / SquareFoot.fromSIUnit(1.toDecimal())
    override fun toSIUnit(value: Decimal): Decimal = UsTonForce.toSIUnit(value * SquareFoot.fromSIUnit(1.toDecimal()))
}

@Serializable
data class USCustomaryImperialPressureWrapper(val imperial: ImperialPressure) : USCustomaryPressure() {
    override val symbol: String = imperial.symbol
    override fun fromSIUnit(value: Decimal): Decimal = imperial.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = imperial.toSIUnit(value)
}

val <PressureUnit : ImperialPressure> PressureUnit.usCustomary get() = USCustomaryImperialPressureWrapper(this)

@Serializable
object ImperialTonSquareInch : UKImperialPressure() {
    override val symbol: String = "${ImperialTonForce.symbol}/${SquareInch.symbol}"
    override fun fromSIUnit(value: Decimal): Decimal = ImperialTonForce.fromSIUnit(value) / SquareInch.fromSIUnit(1.toDecimal())
    override fun toSIUnit(value: Decimal): Decimal = ImperialTonForce.toSIUnit(value * SquareInch.fromSIUnit(1.toDecimal()))
}

@Serializable
object ImperialTonSquareFoot : UKImperialPressure() {
    override val symbol: String = "${ImperialTonForce.symbol}/${SquareFoot.symbol}"
    override fun fromSIUnit(value: Decimal): Decimal = ImperialTonForce.fromSIUnit(value) / SquareFoot.fromSIUnit(1.toDecimal())
    override fun toSIUnit(value: Decimal): Decimal = ImperialTonForce.toSIUnit(value * SquareFoot.fromSIUnit(1.toDecimal()))
}

@Serializable
data class UKImperialPressureWrapper(val imperial: ImperialPressure) : UKImperialPressure() {
    override val symbol: String = imperial.symbol
    override fun fromSIUnit(value: Decimal): Decimal = imperial.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = imperial.toSIUnit(value)
}

val <PressureUnit : ImperialPressure> PressureUnit.ukImperial get() = UKImperialPressureWrapper(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val RadioactivityUnits: Set<Radioactivity> get() = setOf(
    Becquerel,
    Nanobecquerel,
    Microbecquerel,
    Millibecquerel,
    Centibecquerel,
    Decibecquerel,
    Decabecquerel,
    Hectobecquerel,
    Kilobecquerel,
    Megabecquerel,
    Gigabecquerel,
    Rutherford,
    Curie,
    Nanocurie,
    Microcurie,
    Millicurie,
    Centicurie,
    Decicurie,
    Decacurie,
    Hectocurie,
    Kilocurie,
    Megacurie,
    Gigacurie
)

@Serializable
sealed class Radioactivity : AbstractScientificUnit<PhysicalQuantity.Radioactivity>(), MetricAndImperialScientificUnit<PhysicalQuantity.Radioactivity>

@Serializable
object Becquerel : Radioactivity(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity> {
    override val symbol = "Bq"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Radioactivity
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanobecquerel : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Becquerel> by Nano(Becquerel)
@Serializable
object Microbecquerel : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Becquerel> by Micro(Becquerel)
@Serializable
object Millibecquerel : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Becquerel> by Milli(Becquerel)
@Serializable
object Centibecquerel : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Becquerel> by Centi(Becquerel)
@Serializable
object Decibecquerel : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Becquerel> by Deci(Becquerel)
@Serializable
object Decabecquerel : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Becquerel> by Deca(Becquerel)
@Serializable
object Hectobecquerel : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Becquerel> by Hecto(Becquerel)
@Serializable
object Kilobecquerel : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Becquerel> by Kilo(Becquerel)
@Serializable
object Megabecquerel : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Becquerel> by Mega(Becquerel)
@Serializable
object Gigabecquerel : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Becquerel> by Giga(Becquerel)

@Serializable
object Rutherford : Radioactivity() {
    private const val RUTHERFORD_IN_BECQUEREL = 0.000001
    override val symbol = "Rd"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Radioactivity
    override fun fromSIUnit(value: Decimal): Decimal = value * RUTHERFORD_IN_BECQUEREL.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value / RUTHERFORD_IN_BECQUEREL.toDecimal()
}

@Serializable
object Curie : Radioactivity(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity> {
    private const val BECQUEREL_IN_CURIE = 3.7e10
    override val symbol = "Ci"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Radioactivity
    override fun fromSIUnit(value: Decimal): Decimal = value / BECQUEREL_IN_CURIE.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value * BECQUEREL_IN_CURIE.toDecimal()
}

@Serializable
object Nanocurie : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Curie> by Nano(Curie)
@Serializable
object Microcurie : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Curie> by Micro(Curie)
@Serializable
object Millicurie : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Curie> by Milli(Curie)
@Serializable
object Centicurie : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Curie> by Centi(Curie)
@Serializable
object Decicurie : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Curie> by Deci(Curie)
@Serializable
object Decacurie : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Curie> by Deca(Curie)
@Serializable
object Hectocurie : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Curie> by Hecto(Curie)
@Serializable
object Kilocurie : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Curie> by Kilo(Curie)
@Serializable
object Megacurie : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Curie> by Mega(Curie)
@Serializable
object Gigacurie : Radioactivity(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Radioactivity, Curie> by Giga(Curie)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.RoundingMode
import com.splendo.kaluga.base.utils.round
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.base.utils.toDouble
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

sealed interface ScientificUnit<Quantity : PhysicalQuantity> : MeasurementUsage, com.splendo.kaluga.base.utils.Serializable {
    val symbol: String
    val system: MeasurementSystem
    val quantity: Quantity
    fun toSIUnit(value: Decimal): Decimal
    fun fromSIUnit(value: Decimal): Decimal
}

interface SystemScientificUnit<System : MeasurementSystem, Quantity : PhysicalQuantity> : ScientificUnit<Quantity> {
    override val system: System
}

interface MetricScientificUnit<Quantity : PhysicalQuantity> : SystemScientificUnit<MeasurementSystem.Metric, Quantity>, MeasurementUsage.UsedInMetric
interface ImperialScientificUnit<Quantity : PhysicalQuantity> : SystemScientificUnit<MeasurementSystem.Imperial, Quantity>, MeasurementUsage.UsedInImperial
interface USCustomaryScientificUnit<Quantity : PhysicalQuantity> : SystemScientificUnit<MeasurementSystem.USCustomary, Quantity>, MeasurementUsage.UsedInUSCustomary
interface UKImperialScientificUnit<Quantity : PhysicalQuantity> : SystemScientificUnit<MeasurementSystem.UKImperial, Quantity>, MeasurementUsage.UsedInUKImperial
interface MetricAndUKImperialScientificUnit<Quantity : PhysicalQuantity> : SystemScientificUnit<MeasurementSystem.MetricAndUKImperial, Quantity>, MeasurementUsage.UsedInMetricAndUKImperial
interface MetricAndImperialScientificUnit<Quantity : PhysicalQuantity> : SystemScientificUnit<MeasurementSystem.MetricAndImperial, Quantity>, MeasurementUsage.UsedInMetricAndImperial

@Serializable
sealed class AbstractScientificUnit<Quantity : PhysicalQuantity> : ScientificUnit<Quantity>

fun <Quantity : PhysicalQuantity> ScientificUnit<Quantity>.convert(
    value: Number,
    to: ScientificUnit<Quantity>
) = convert(value.toDecimal(), to).toDouble()

fun <Quantity : PhysicalQuantity> ScientificUnit<Quantity>.convert(
    value: Number,
    to: ScientificUnit<Quantity>,
    round: Int,
    roundingMode: RoundingMode = RoundingMode.RoundHalfEven
) = convert(value.toDecimal(), to).round(round, roundingMode).toDouble()

fun <Quantity : PhysicalQuantity> ScientificUnit<Quantity>.convert(
    value: Decimal,
    to: ScientificUnit<Quantity>,
    round: Int,
    roundingMode: RoundingMode = RoundingMode.RoundHalfEven
) = convert(value, to).round(round, roundingMode)

fun <Quantity : PhysicalQuantity> ScientificUnit<Quantity>.convert(
    value: Decimal,
    to: ScientificUnit<Quantity>
) = if (this == to) value else to.fromSIUnit(toSIUnit(value))

val Units: Set<ScientificUnit<*>> get() = AccelerationUnits +
    ActionUnits +
    AmountOfSubstanceUnits +
    AngleUnits +
    AngularAccelerationUnits +
    AngularVelocityUnits +
    AreaDensityUnits +
    AreaUnits +
    CatalysticActivityUnits +
    DensityUnits +
    DynamicViscosityUnits +
    ElectricCapacitanceUnits +
    ElectricChargeUnits +
    ElectricConductanceUnits +
    ElectricCurrentUnits +
    ElectricInductanceUnits +
    ElectricResistanceUnits +
    EnergyUnits +
    ForceUnits +
    FrequencyUnits +
    HeatCapacityUnits +
    IlluminanceUnits +
    IonizingRadiationAbsorbedDoseUnits +
    IonizingRadiationEquivalentDoseUnits +
    JoltUnits +
    LengthUnits +
    LinearMassDensityUnits +
    LuminanceUnits +
    LuminousEnergyUnits +
    LuminousExposureUnits +
    LuminousFluxUnits +
    LuminousIntensityUnits +
    MagneticFluxUnits +
    MagneticInductionUnits +
    MassFlowRateUnits +
    MolalityUnits +
    MolarEnergyUnits +
    MolarityUnits +
    MolarMassUnits +
    MolarVolumeUnits +
    MomentumUnits +
    PowerUnits +
    PressureUnits +
    RadioactivityUnits +
    SolidAngleUnits +
    SpecificEnergyUnits +
    SpecificHeatCapacityUnits +
    SpecificVolumeUnits +
    SpeedUnits +
    SurfaceTensionUnits +
    TemperatureUnits +
    ThermalResistanceUnits +
    TimeUnits +
    VoltageUnits +
    VolumetricFlowUnits +
    VolumetricFluxUnits +
    VolumeUnits +
    WeightUnits +
    YankUnits

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable
import kotlin.math.PI

val SolidAngleUnits: Set<SolidAngle> get() = setOf(
    Steradian,
    Nanosteradian,
    Microsteradian,
    Millisteradian,
    Centisteradian,
    Decisteradian,
    Spat,
    SquareDegree
)

@Serializable
sealed class SolidAngle : AbstractScientificUnit<PhysicalQuantity.SolidAngle>(), MetricAndImperialScientificUnit<PhysicalQuantity.SolidAngle>

@Serializable
object Steradian : SolidAngle(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.SolidAngle> {
    override val symbol = "sr"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.SolidAngle
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanosteradian : SolidAngle(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.SolidAngle, Steradian> by Nano(Steradian)
@Serializable
object Microsteradian : SolidAngle(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.SolidAngle, Steradian> by Micro(Steradian)
@Serializable
object Millisteradian : SolidAngle(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.SolidAngle, Steradian> by Milli(Steradian)
@Serializable
object Centisteradian : SolidAngle(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.SolidAngle, Steradian> by Centi(Steradian)
@Serializable
object Decisteradian : SolidAngle(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.SolidAngle, Steradian> by Deci(Steradian)

@Serializable
object Spat : SolidAngle(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.SolidAngle> {
    private const val STERADIAN_IN_SPAT = 4.0 * PI
    override val symbol = "sp"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.SolidAngle
    override fun fromSIUnit(value: Decimal): Decimal = value / STERADIAN_IN_SPAT.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value * STERADIAN_IN_SPAT.toDecimal()
}

@Serializable
object SquareDegree : SolidAngle() {
    override val symbol = "°2"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.SolidAngle
    override fun fromSIUnit(value: Decimal): Decimal = Degree.fromSIUnit(Degree.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = Degree.toSIUnit(Degree.toSIUnit(value))
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricSpecificEnergyUnits: Set<MetricSpecificEnergy> get() = MetricEnergyUnits.flatMap { energy ->
    MetricWeightUnits.map { energy per it }
}.toSet()

val ImperialSpecificEnergyUnits: Set<ImperialSpecificEnergy> get() = ImperialEnergyUnits.flatMap { energy ->
    ImperialWeightUnits.map { energy per it }
}.toSet()

val UKImperialSpecificEnergyUnits: Set<UKImperialSpecificEnergy> get() = ImperialEnergyUnits.flatMap { energy ->
    UKImperialWeightUnits.map { energy per it }
}.toSet()

val USCustomarySpecificEnergyUnits: Set<USCustomarySpecificEnergy> get() = ImperialEnergyUnits.flatMap { energy ->
    USCustomaryWeightUnits.map { energy per it }
}.toSet()

val SpecificEnergyUnits: Set<SpecificEnergy> get() = MetricSpecificEnergyUnits +
    ImperialSpecificEnergyUnits +
    UKImperialSpecificEnergyUnits.filter { it.per !is UKImperialImperialWeightWrapper }.toSet() +
    USCustomarySpecificEnergyUnits.filter { it.per !is USCustomaryImperialWeightWrapper }.toSet()

@Serializable
sealed class SpecificEnergy : AbstractScientificUnit<PhysicalQuantity.SpecificEnergy>() {
    abstract val energy: Energy
    abstract val per: Weight
    override val quantity = PhysicalQuantity.SpecificEnergy
    override val symbol: String by lazy { "${energy.symbol}/${per.symbol}" }
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(energy.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = energy.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricSpecificEnergy(override val energy: MetricEnergy, override val per: MetricWeight) : SpecificEnergy(), MetricScientificUnit<PhysicalQuantity.SpecificEnergy> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialSpecificEnergy(override val energy: ImperialEnergy, override val per: ImperialWeight) : SpecificEnergy(), ImperialScientificUnit<PhysicalQuantity.SpecificEnergy> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = energy per per.ukImperial
    val usCustomary get() = energy per per.usCustomary
}
@Serializable
data class UKImperialSpecificEnergy(override val energy: ImperialEnergy, override val per: UKImperialWeight) : SpecificEnergy(), UKImperialScientificUnit<PhysicalQuantity.SpecificEnergy> {
    override val system = MeasurementSystem.UKImperial
}
@Serializable
data class USCustomarySpecificEnergy(override val energy: ImperialEnergy, override val per: USCustomaryWeight) : SpecificEnergy(), USCustomaryScientificUnit<PhysicalQuantity.SpecificEnergy> {
    override val system = MeasurementSystem.USCustomary
}

infix fun MetricAndImperialEnergy.per(weight: MetricWeight) = MetricSpecificEnergy(this.metric, weight)
infix fun MetricAndImperialEnergy.per(weight: ImperialWeight) = ImperialSpecificEnergy(this.imperial, weight)
infix fun MetricAndImperialEnergy.per(weight: UKImperialWeight) = UKImperialSpecificEnergy(this.imperial, weight)
infix fun MetricAndImperialEnergy.per(weight: USCustomaryWeight) = USCustomarySpecificEnergy(this.imperial, weight)
infix fun MetricEnergy.per(weight: MetricWeight) = MetricSpecificEnergy(this, weight)
infix fun ImperialEnergy.per(weight: ImperialWeight) = ImperialSpecificEnergy(this, weight)
infix fun ImperialEnergy.per(weight: UKImperialWeight) = UKImperialSpecificEnergy(this, weight)
infix fun ImperialEnergy.per(weight: USCustomaryWeight) = USCustomarySpecificEnergy(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricSpecificHeatCapacityUnits: Set<MetricSpecificHeatCapacity> get() = MetricHeatCapacityUnits.flatMap { heatCapacity ->
    MetricWeightUnits.map { heatCapacity per it }
}.toSet()

val UKImperialSpecificHeatCapacityUnits: Set<UKImperialSpecificHeatCapacity> get() = UKImperialHeatCapacityUnits.flatMap { heatCapacity ->
    UKImperialWeightUnits.map { heatCapacity per it }
}.toSet()

val USCustomarySpecificHeatCapacityUnits: Set<USCustomarySpecificHeatCapacity> get() = USCustomaryHeatCapacityUnits.flatMap { heatCapacity ->
    USCustomaryWeightUnits.map { heatCapacity per it }
}.toSet()

val SpecificHeatCapacityUnits: Set<SpecificHeatCapacity> get() = MetricSpecificHeatCapacityUnits +
    UKImperialSpecificHeatCapacityUnits +
    USCustomarySpecificHeatCapacityUnits

@Serializable
sealed class SpecificHeatCapacity : AbstractScientificUnit<PhysicalQuantity.SpecificHeatCapacity>() {
    abstract val heatCapacity: HeatCapacity
    abstract val perWeight: Weight
    override val quantity = PhysicalQuantity.SpecificHeatCapacity
    override val symbol: String by lazy { "${heatCapacity.energy.symbol}/(${heatCapacity.per.symbol}⋅${perWeight.symbol})" }
    override fun fromSIUnit(value: Decimal): Decimal = perWeight.toSIUnit(heatCapacity.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = heatCapacity.toSIUnit(perWeight.fromSIUnit(value))
}

@Serializable
data class MetricSpecificHeatCapacity(override val heatCapacity: MetricHeatCapacity, override val perWeight: MetricWeight) : SpecificHeatCapacity(), MetricScientificUnit<PhysicalQuantity.SpecificHeatCapacity> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class UKImperialSpecificHeatCapacity(override val heatCapacity: UKImperialHeatCapacity, override val perWeight: UKImperialWeight) : SpecificHeatCapacity(), UKImperialScientificUnit<PhysicalQuantity.SpecificHeatCapacity> {
    override val system = MeasurementSystem.UKImperial
}
@Serializable
data class USCustomarySpecificHeatCapacity(override val heatCapacity: USCustomaryHeatCapacity, override val perWeight: USCustomaryWeight) : SpecificHeatCapacity(), USCustomaryScientificUnit<PhysicalQuantity.SpecificHeatCapacity> {
    override val system = MeasurementSystem.USCustomary
}

infix fun MetricAndUKImperialHeatCapacity.per(weight: MetricWeight) = MetricSpecificHeatCapacity(metric, weight)
infix fun MetricAndUKImperialHeatCapacity.per(weight: ImperialWeight) = UKImperialSpecificHeatCapacity(ukImperial, weight.ukImperial)
infix fun MetricAndUKImperialHeatCapacity.per(weight: UKImperialWeight) = UKImperialSpecificHeatCapacity(ukImperial, weight)
infix fun MetricHeatCapacity.per(weight: MetricWeight) = MetricSpecificHeatCapacity(this, weight)
infix fun UKImperialHeatCapacity.per(weight: ImperialWeight) = UKImperialSpecificHeatCapacity(this, weight.ukImperial)
infix fun UKImperialHeatCapacity.per(weight: UKImperialWeight) = UKImperialSpecificHeatCapacity(this, weight)
infix fun USCustomaryHeatCapacity.per(weight: ImperialWeight) = USCustomarySpecificHeatCapacity(this, weight.usCustomary)
infix fun USCustomaryHeatCapacity.per(weight: USCustomaryWeight) = USCustomarySpecificHeatCapacity(this, weight)

infix fun MetricSpecificEnergy.per(temperature: MetricAndUKImperialTemperature) = MetricSpecificHeatCapacity(energy per temperature, per)
infix fun ImperialSpecificEnergy.per(temperature: MetricAndUKImperialTemperature) = UKImperialSpecificHeatCapacity(energy per temperature, per.ukImperial)
infix fun ImperialSpecificEnergy.per(temperature: USCustomaryTemperature) = USCustomarySpecificHeatCapacity(energy per temperature, per.usCustomary)
infix fun UKImperialSpecificEnergy.per(temperature: MetricAndUKImperialTemperature) = UKImperialSpecificHeatCapacity(energy per temperature, per)
infix fun USCustomarySpecificEnergy.per(temperature: USCustomaryTemperature) = USCustomarySpecificHeatCapacity(energy per temperature, per)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricSpecificVolumeUnits: Set<MetricSpecificVolume> get() = MetricVolumeUnits.flatMap { volume ->
    MetricWeightUnits.map { volume per it }
}.toSet()

val ImperialSpecificVolumeUnits: Set<ImperialSpecificVolume> get() = ImperialVolumeUnits.flatMap { volume ->
    ImperialWeightUnits.map { volume per it }
}.toSet()

val UKImperialSpecificVolumeUnits: Set<UKImperialSpecificVolume> get() = UKImperialVolumeUnits.flatMap { volume ->
    UKImperialWeightUnits.map { volume per it }
}.toSet()

val USCustomarySpecificVolumeUnits: Set<USCustomarySpecificVolume> get() = USCustomaryVolumeUnits.flatMap { volume ->
    USCustomaryWeightUnits.map { volume per it }
}.toSet()

val SpecificVolumeUnits: Set<SpecificVolume> get() = MetricSpecificVolumeUnits +
    ImperialSpecificVolumeUnits +
    UKImperialSpecificVolumeUnits.filter { it.volume !is UKImperialImperialVolumeWrapper || it.per !is UKImperialImperialWeightWrapper }.toSet() +
    USCustomarySpecificVolumeUnits.filter { it.volume !is USCustomaryImperialVolumeWrapper || it.per !is USCustomaryImperialWeightWrapper }.toSet()

@Serializable
sealed class SpecificVolume : AbstractScientificUnit<PhysicalQuantity.SpecificVolume>() {
    abstract val volume: Volume
    abstract val per: Weight
    override val symbol: String by lazy { "${volume.symbol} / ${per.symbol}" }
    override val quantity = PhysicalQuantity.SpecificVolume
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(volume.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = volume.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricSpecificVolume(override val volume: MetricVolume, override val per: MetricWeight) : SpecificVolume(), MetricScientificUnit<PhysicalQuantity.SpecificVolume> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialSpecificVolume(override val volume: ImperialVolume, override val per: ImperialWeight) : SpecificVolume(), ImperialScientificUnit<PhysicalQuantity.SpecificVolume> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = volume.ukImperial per per.ukImperial
    val usCustomary get() = volume.usCustomary per per.usCustomary
}
@Serializable
data class USCustomarySpecificVolume(override val volume: USCustomaryVolume, override val per: USCustomaryWeight) : SpecificVolume(), USCustomaryScientificUnit<PhysicalQuantity.SpecificVolume> {
    override val system = MeasurementSystem.USCustomary
}
@Serializable
data class UKImperialSpecificVolume(override val volume: UKImperialVolume, override val per: UKImperialWeight) : SpecificVolume(), UKImperialScientificUnit<PhysicalQuantity.SpecificVolume> {
    override val system = MeasurementSystem.UKImperial
}

infix fun MetricVolume.per(weight: MetricWeight) = MetricSpecificVolume(this, weight)
infix fun ImperialVolume.per(weight: ImperialWeight) = ImperialSpecificVolume(this, weight)
infix fun ImperialVolume.per(weight: UKImperialWeight) = UKImperialSpecificVolume(this.ukImperial, weight)
infix fun ImperialVolume.per(weight: USCustomaryWeight) = USCustomarySpecificVolume(this.usCustomary, weight)
infix fun USCustomaryVolume.per(weight: USCustomaryWeight) = USCustomarySpecificVolume(this, weight)
infix fun USCustomaryVolume.per(weight: ImperialWeight) = USCustomarySpecificVolume(this, weight.usCustomary)
infix fun UKImperialVolume.per(weight: ImperialWeight) = UKImperialSpecificVolume(this, weight.ukImperial)
infix fun UKImperialVolume.per(weight: UKImperialWeight) = UKImperialSpecificVolume(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.convert
import com.splendo.kaluga.scientific.invoke
import kotlinx.serialization.Serializable
import kotlin.native.concurrent.ThreadLocal

val MetricSpeedUnits: Set<MetricSpeed> get() = MetricLengthUnits.flatMap { length ->
    TimeUnits.map { MetricSpeed(length, it) }
}.toSet()

val ImperialSpeedUnits: Set<ImperialSpeed> get() = ImperialLengthUnits.flatMap { length ->
    TimeUnits.map { ImperialSpeed(length, it) }
}.toSet()

val SpeedUnits: Set<Speed> get() = MetricSpeedUnits + ImperialSpeedUnits

@Serializable
sealed class Speed : AbstractScientificUnit<PhysicalQuantity.Speed>() {
    abstract val distance: Length
    abstract val per: Time
    override val quantity = PhysicalQuantity.Speed
    override val symbol: String by lazy { "${distance.symbol} / ${per.symbol}" }
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(distance.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = distance.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricSpeed(override val distance: MetricLength, override val per: Time) : Speed(), MetricScientificUnit<PhysicalQuantity.Speed> {
    override val system = MeasurementSystem.Metric
}

@Serializable
data class ImperialSpeed(override val distance: ImperialLength, override val per: Time) : Speed(), ImperialScientificUnit<PhysicalQuantity.Speed> {
    override val system = MeasurementSystem.Imperial
}

infix fun MetricLength.per(time: Time) = MetricSpeed(this, time)
infix fun ImperialLength.per(time: Time) = ImperialSpeed(this, time)

@ThreadLocal
val MetricSpeedOfLight = 299792458(Meter per Second)
@ThreadLocal
val ImperialSpeedOfLight = MetricSpeedOfLight.convert(Foot per Second)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricSurfaceTensionUnits: Set<MetricSurfaceTension> get() = MetricForceUnits.flatMap { force ->
    MetricLengthUnits.map { force per it }
}.toSet()

val ImperialSurfaceTensionUnits: Set<ImperialSurfaceTension> get() = ImperialForceUnits.flatMap { force ->
    ImperialLengthUnits.map { force per it }
}.toSet()

val UKImperialSurfaceTensionUnits: Set<UKImperialSurfaceTension> get() = UKImperialForceUnits.flatMap { force ->
    ImperialLengthUnits.map { force per it }
}.toSet()

val USCustomarySurfaceTensionUnits: Set<USCustomarySurfaceTension> get() = USCustomaryForceUnits.flatMap { force ->
    ImperialLengthUnits.map { force per it }
}.toSet()

val SurfaceTensionUnits: Set<SurfaceTension> get() = MetricSurfaceTensionUnits +
    ImperialSurfaceTensionUnits +
    UKImperialSurfaceTensionUnits.filter { it.force !is UKImperialImperialForceWrapper }.toSet() +
    USCustomarySurfaceTensionUnits.filter { it.force !is USCustomaryImperialForceWrapper }.toSet()

@Serializable
sealed class SurfaceTension : AbstractScientificUnit<PhysicalQuantity.SurfaceTension>() {
    abstract val force: Force
    abstract val per: Length
    override val symbol: String by lazy { "${force.symbol} / ${per.symbol}" }
    override val quantity = PhysicalQuantity.SurfaceTension
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(force.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = force.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricSurfaceTension(override val force: MetricForce, override val per: MetricLength) : SurfaceTension(), MetricScientificUnit<PhysicalQuantity.SurfaceTension> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialSurfaceTension(override val force: ImperialForce, override val per: ImperialLength) : SurfaceTension(), ImperialScientificUnit<PhysicalQuantity.SurfaceTension> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = force.ukImperial per per
    val usCustomary get() = force.usCustomary per per
}
@Serializable
data class USCustomarySurfaceTension(override val force: USCustomaryForce, override val per: ImperialLength) : SurfaceTension(), USCustomaryScientificUnit<PhysicalQuantity.SurfaceTension> {
    override val system = MeasurementSystem.USCustomary
}
@Serializable
data class UKImperialSurfaceTension(override val force: UKImperialForce, override val per: ImperialLength) : SurfaceTension(), UKImperialScientificUnit<PhysicalQuantity.SurfaceTension> {
    override val system = MeasurementSystem.UKImperial
}

infix fun MetricForce.per(length: MetricLength) = MetricSurfaceTension(this, length)
infix fun ImperialForce.per(length: ImperialLength) = ImperialSurfaceTension(this, length)
infix fun USCustomaryForce.per(length: ImperialLength) = USCustomarySurfaceTension(this, length)
infix fun UKImperialForce.per(length: ImperialLength) = UKImperialSurfaceTension(this, length)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.RoundingMode
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.minus
import com.splendo.kaluga.base.utils.plus
import com.splendo.kaluga.base.utils.round
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.base.utils.toDouble
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricAndUkImperialTemperatureUnits: Set<MetricAndUKImperialTemperature> get() = setOf(Kelvin, Celsius)
val USCustomaryTemperatureUnits: Set<USCustomaryTemperature> get() = setOf(Rankine, Fahrenheit)
val TemperatureUnits: Set<Temperature> get() = MetricAndUkImperialTemperatureUnits + USCustomaryTemperatureUnits

@Serializable
sealed class Temperature : AbstractScientificUnit<PhysicalQuantity.Temperature>() {
    override val quantity = PhysicalQuantity.Temperature
    abstract fun deltaToSIUnitDelta(delta: Decimal): Decimal
    abstract fun deltaFromSIUnitDelta(delta: Decimal): Decimal
}

@Serializable
sealed class MetricAndUKImperialTemperature(override val symbol: String) : Temperature(), MetricAndUKImperialScientificUnit<PhysicalQuantity.Temperature>, MeasurementUsage.UsedInUKImperial {
    override val system = MeasurementSystem.MetricAndUKImperial
}

@Serializable
sealed class USCustomaryTemperature(override val symbol: String) : Temperature(), USCustomaryScientificUnit<PhysicalQuantity.Temperature> {
    override val system = MeasurementSystem.USCustomary
}

@Serializable
object Celsius : MetricAndUKImperialTemperature("°C") {
    override fun toSIUnit(value: Decimal): Decimal = value + Kelvin.KELVIN_FREEZING.toDecimal()
    override fun fromSIUnit(value: Decimal): Decimal = value - Kelvin.KELVIN_FREEZING.toDecimal()
    override fun deltaToSIUnitDelta(delta: Decimal): Decimal = Kelvin.toSIUnit(delta)
    override fun deltaFromSIUnitDelta(delta: Decimal): Decimal = Kelvin.fromSIUnit(delta)
}

@Serializable
object Kelvin : MetricAndUKImperialTemperature("K") {
    internal const val KELVIN_FREEZING = 273.15
    override fun toSIUnit(value: Decimal): Decimal = value
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun deltaToSIUnitDelta(delta: Decimal): Decimal = toSIUnit(delta)
    override fun deltaFromSIUnitDelta(delta: Decimal): Decimal = fromSIUnit(delta)
}

@Serializable
object Fahrenheit : USCustomaryTemperature("°F") {
    override fun toSIUnit(value: Decimal): Decimal = Rankine.toSIUnit(value + Rankine.RANKINE_FREEZING.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = Rankine.fromSIUnit(value) - Rankine.RANKINE_FREEZING.toDecimal()
    override fun deltaToSIUnitDelta(delta: Decimal): Decimal = Rankine.toSIUnit(delta)
    override fun deltaFromSIUnitDelta(delta: Decimal): Decimal = Rankine.fromSIUnit(delta)
}

@Serializable
object Rankine : USCustomaryTemperature("°R") {
    internal const val RANKINE_FREEZING = 459.67
    private const val FAHRENHEIT_INCREASE_PER_CELSIUS_INCREASE = 5.0 / 9.0
    override fun toSIUnit(value: Decimal): Decimal = value * FAHRENHEIT_INCREASE_PER_CELSIUS_INCREASE.toDecimal()
    override fun fromSIUnit(value: Decimal): Decimal = value / FAHRENHEIT_INCREASE_PER_CELSIUS_INCREASE.toDecimal()
    override fun deltaToSIUnitDelta(delta: Decimal): Decimal = toSIUnit(delta)
    override fun deltaFromSIUnitDelta(delta: Decimal): Decimal = fromSIUnit(delta)
}

fun Temperature.convertDelta(
    value: Number,
    to: Temperature
) = convertDelta(value.toDecimal(), to).toDouble()

fun Temperature.convertDelta(
    value: Number,
    to: Temperature,
    round: Int,
    roundingMode: RoundingMode = RoundingMode.RoundHalfEven
) = convertDelta(value.toDecimal(), to).round(round, roundingMode).toDouble()

fun Temperature.convertDelta(
    value: Decimal,
    to: Temperature
) = if (this == to) value else to.deltaFromSIUnitDelta(deltaToSIUnitDelta(value))

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricAndUKImperialThermalResistanceUnits: Set<MetricAndUKImperialThermalResistance> get() = MetricAndUkImperialTemperatureUnits.flatMap { temperature ->
    MetricAndImperialPowerUnits.map { temperature per it }
}.toSet()

val MetricThermalResistanceUnits: Set<MetricThermalResistance> get() = MetricAndUkImperialTemperatureUnits.flatMap { temperature ->
    MetricPowerUnits.map { temperature per it }
}.toSet()

val UKImperialThermalResistanceUnits: Set<UKImperialThermalResistance> get() = MetricAndUkImperialTemperatureUnits.flatMap { temperature ->
    ImperialPowerUnits.map { temperature per it }
}.toSet()

val USCustomaryThermalResistanceUnits: Set<USCustomaryThermalResistance> get() = USCustomaryTemperatureUnits.flatMap { temperature ->
    ImperialPowerUnits.map { temperature per it }
}.toSet()

val ThermalResistanceUnits: Set<ThermalResistance> get() = MetricAndUKImperialThermalResistanceUnits +
    MetricThermalResistanceUnits.filter { it.per !is MetricMetricAndImperialPowerWrapper }.toSet() +
    UKImperialThermalResistanceUnits.filter { it.per !is ImperialMetricAndImperialPowerWrapper }.toSet() +
    USCustomaryThermalResistanceUnits

@Serializable
sealed class ThermalResistance : AbstractScientificUnit<PhysicalQuantity.ThermalResistance>() {
    abstract val temperature: Temperature
    abstract val per: Power
    override val quantity = PhysicalQuantity.ThermalResistance
    override val symbol: String by lazy { "${temperature.symbol}/${per.symbol}" }
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(temperature.deltaFromSIUnitDelta(value))
    override fun toSIUnit(value: Decimal): Decimal = temperature.deltaToSIUnitDelta(per.fromSIUnit(value))
}

@Serializable
data class MetricAndUKImperialThermalResistance(override val temperature: MetricAndUKImperialTemperature, override val per: MetricAndImperialPower) : ThermalResistance(), MetricAndUKImperialScientificUnit<PhysicalQuantity.ThermalResistance> {
    override val system = MeasurementSystem.MetricAndUKImperial
    val metric get() = temperature per per.metric
    val ukImperial get() = temperature per per.imperial
}
@Serializable
data class MetricThermalResistance(override val temperature: MetricAndUKImperialTemperature, override val per: MetricPower) : ThermalResistance(), MetricScientificUnit<PhysicalQuantity.ThermalResistance> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class UKImperialThermalResistance(override val temperature: MetricAndUKImperialTemperature, override val per: ImperialPower) : ThermalResistance(), UKImperialScientificUnit<PhysicalQuantity.ThermalResistance> {
    override val system = MeasurementSystem.UKImperial
}
@Serializable
data class USCustomaryThermalResistance(override val temperature: USCustomaryTemperature, override val per: ImperialPower) : ThermalResistance(), USCustomaryScientificUnit<PhysicalQuantity.ThermalResistance> {
    override val system = MeasurementSystem.USCustomary
}

infix fun MetricAndUKImperialTemperature.per(power: MetricAndImperialPower) = MetricAndUKImperialThermalResistance(this, power)
infix fun MetricAndUKImperialTemperature.per(power: MetricPower) = MetricThermalResistance(this, power)
infix fun MetricAndUKImperialTemperature.per(power: ImperialPower) = UKImperialThermalResistance(this, power)
infix fun USCustomaryTemperature.per(power: MetricAndImperialPower) = USCustomaryThermalResistance(this, power.imperial)
infix fun USCustomaryTemperature.per(power: ImperialPower) = USCustomaryThermalResistance(this, power)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val TimeUnits: Set<Time> get() = setOf(
    Second,
    Nanosecond,
    Microsecond,
    Millisecond,
    Centisecond,
    Decisecond,
    Minute,
    Hour
)

@Serializable
sealed class Time : AbstractScientificUnit<PhysicalQuantity.Time>(), MetricAndImperialScientificUnit<PhysicalQuantity.Time>

@Serializable
object Second : Time(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Time> {
    override val symbol = "s"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Time
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Decisecond : Time(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Time, Second> by Deci(Second)

@Serializable
object Centisecond : Time(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Time, Second> by Centi(Second)

@Serializable
object Millisecond : Time(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Time, Second> by Milli(Second)

@Serializable
object Microsecond : Time(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Time, Second> by Micro(Second)

@Serializable
object Nanosecond : Time(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Time, Second> by Nano(Second)

@Serializable
object Minute : Time() {
    private const val SECOND_PER_MINUTE = 60.0
    override val symbol: String = "min"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Time
    override fun fromSIUnit(value: Decimal): Decimal = value / SECOND_PER_MINUTE.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value * SECOND_PER_MINUTE.toDecimal()
}

@Serializable
object Hour : Time() {
    private const val SECOND_PER_HOUR = 3600.0
    override val symbol: String = "h"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Time
    override fun fromSIUnit(value: Decimal): Decimal = value / SECOND_PER_HOUR.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value * SECOND_PER_HOUR.toDecimal()
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val VoltageUnits: Set<Voltage> get() = setOf(
    Volt,
    Nanovolt,
    Microvolt,
    Millivolt,
    Centivolt,
    Decivolt,
    Decavolt,
    Hectovolt,
    Kilovolt,
    Megavolt,
    Gigavolt,
    Abvolt
)

@Serializable
sealed class Voltage : AbstractScientificUnit<PhysicalQuantity.Voltage>(), MetricAndImperialScientificUnit<PhysicalQuantity.Voltage>

@Serializable
object Volt : Voltage(), MetricBaseUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Voltage> {
    override val symbol = "V"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Voltage
    override fun fromSIUnit(value: Decimal): Decimal = value
    override fun toSIUnit(value: Decimal): Decimal = value
}

@Serializable
object Nanovolt : Voltage(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Voltage, Volt> by Nano(Volt)
@Serializable
object Abvolt : Voltage() {
    private const val ABVOLT_IN_VOLT = 100000000.0
    override val symbol = "abV"
    override val system = MeasurementSystem.MetricAndImperial
    override val quantity = PhysicalQuantity.Voltage
    override fun fromSIUnit(value: Decimal): Decimal = value * ABVOLT_IN_VOLT.toDecimal()
    override fun toSIUnit(value: Decimal): Decimal = value / ABVOLT_IN_VOLT.toDecimal()
}
@Serializable
object Microvolt : Voltage(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Voltage, Volt> by Micro(Volt)
@Serializable
object Millivolt : Voltage(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Voltage, Volt> by Milli(Volt)
@Serializable
object Centivolt : Voltage(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Voltage, Volt> by Centi(Volt)
@Serializable
object Decivolt : Voltage(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Voltage, Volt> by Deci(Volt)
@Serializable
object Decavolt : Voltage(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Voltage, Volt> by Deca(Volt)
@Serializable
object Hectovolt : Voltage(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Voltage, Volt> by Hecto(Volt)
@Serializable
object Kilovolt : Voltage(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Voltage, Volt> by Kilo(Volt)
@Serializable
object Megavolt : Voltage(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Voltage, Volt> by Mega(Volt)
@Serializable
object Gigavolt : Voltage(), MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Voltage, Volt> by Giga(Volt)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricVolumeUnits: Set<MetricVolume> get() = setOf(
    CubicMeter,
    CubicNanometer,
    CubicMicrometer,
    CubicMillimeter,
    CubicCentimeter,
    CubicDecimeter,
    CubicDecameter,
    CubicHectometer,
    CubicKilometer,
    CubicMegameter,
    CubicGigameter,
    Liter,
    Nanoliter,
    Microliter,
    Milliliter,
    Centiliter,
    Deciliter,
    Decaliter,
    Hectoliter,
    Kiloliter,
    Megaliter,
    Gigaliter
)

val ImperialVolumeUnits: Set<ImperialVolume> get() = setOf(
    CubicInch,
    CubicFoot,
    CubicYard,
    CubicMile
)

val USCustomaryVolumeUnits: Set<USCustomaryVolume> get() = ImperialVolumeUnits.map { it.usCustomary }.toSet() +
    setOf(
        AcreFoot,
        AcreInch,
        UsFluidDram,
        UsFluidOunce,
        UsCustomaryCup,
        UsLegalCup,
        UsLiquidPint,
        UsLiquidQuart,
        UsLiquidGallon
    )

val UKImperialVolumeUnits: Set<UKImperialVolume> get() = ImperialVolumeUnits.map { it.ukImperial }.toSet() +
    setOf(
        ImperialFluidDram,
        ImperialFluidOunce,
        MetricCup,
        ImperialPint,
        ImperialQuart,
        ImperialGallon
    )

val VolumeUnits: Set<Volume> get() = MetricVolumeUnits +
    ImperialVolumeUnits +
    USCustomaryVolumeUnits.filter { it !is USCustomaryImperialVolumeWrapper }.toSet() +
    UKImperialVolumeUnits.filter { it !is UKImperialImperialVolumeWrapper }.toSet()

@Serializable
sealed class Volume : AbstractScientificUnit<PhysicalQuantity.Volume>()

@Serializable
sealed class MetricVolume : Volume(), MetricScientificUnit<PhysicalQuantity.Volume>

@Serializable
sealed class USCustomaryVolume : Volume(), USCustomaryScientificUnit<PhysicalQuantity.Volume> {
    override val quantity = PhysicalQuantity.Volume
    override val system = MeasurementSystem.USCustomary
}

@Serializable
sealed class UKImperialVolume : Volume(), UKImperialScientificUnit<PhysicalQuantity.Volume> {
    override val quantity = PhysicalQuantity.Volume
    override val system = MeasurementSystem.UKImperial
}

@Serializable
sealed class ImperialVolume : Volume(), ImperialScientificUnit<PhysicalQuantity.Volume>

class Cubic<S : MeasurementSystem, U : SystemScientificUnit<S, PhysicalQuantity.Length>>(private val unit: U) : SystemScientificUnit<S, PhysicalQuantity.Volume> {
    override val symbol: String = "${unit.symbol}3"
    override val system: S = unit.system
    override val quantity = PhysicalQuantity.Volume
    override fun fromSIUnit(value: Decimal): Decimal = unit.fromSIUnit(unit.fromSIUnit(unit.fromSIUnit(value)))
    override fun toSIUnit(value: Decimal): Decimal = unit.toSIUnit(unit.toSIUnit(unit.toSIUnit(value)))
}

// Metric Volume
@Serializable
object CubicMeter : MetricVolume(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume> by Cubic(Meter)

@Serializable
object CubicDecimeter : MetricVolume(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume> by Cubic(Deci(Meter))

@Serializable
object CubicCentimeter : MetricVolume(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume> by Cubic(Centi(Meter)) {
    override val symbol: String = "cc"
}

@Serializable
object CubicMillimeter : MetricVolume(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume> by Cubic(Milli(Meter))

@Serializable
object CubicMicrometer : MetricVolume(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume> by Cubic(Micro(Meter))

@Serializable
object CubicNanometer : MetricVolume(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume> by Cubic(Nano(Meter))

@Serializable
object CubicDecameter : MetricVolume(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume> by Cubic(Deca(Meter))

@Serializable
object CubicHectometer : MetricVolume(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume> by Cubic(Hecto(Meter))

@Serializable
object CubicKilometer : MetricVolume(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume> by Cubic(Kilo(Meter))

@Serializable
object CubicMegameter : MetricVolume(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume> by Cubic(Mega(Meter))

@Serializable
object CubicGigameter : MetricVolume(), SystemScientificUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume> by Cubic(Giga(Meter))

@Serializable
object Liter : MetricVolume(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume> {
    override val symbol: String = "l"
    private const val LITERS_IN_CUBIC_METER = 1000.0
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Volume
    override fun toSIUnit(value: Decimal): Decimal = value / LITERS_IN_CUBIC_METER.toDecimal()
    override fun fromSIUnit(value: Decimal): Decimal = value * LITERS_IN_CUBIC_METER.toDecimal()
}

@Serializable
object Deciliter : MetricVolume(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume, Liter> by Deci(Liter)

@Serializable
object Centiliter : MetricVolume(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume, Liter> by Centi(Liter)

@Serializable
object Milliliter : MetricVolume(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume, Liter> by Milli(Liter)

@Serializable
object Microliter : MetricVolume(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume, Liter> by Micro(Liter)

@Serializable
object Nanoliter : MetricVolume(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume, Liter> by Nano(Liter)

@Serializable
object Decaliter : MetricVolume(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume, Liter> by Deca(Liter)

@Serializable
object Hectoliter : MetricVolume(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume, Liter> by Hecto(Liter)

@Serializable
object Kiloliter : MetricVolume(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume, Liter> by Kilo(Liter)

@Serializable
object Megaliter : MetricVolume(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume, Liter> by Mega(Liter)

@Serializable
object Gigaliter : MetricVolume(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Volume, Liter> by Giga(Liter)

// Imperial
@Serializable
object CubicInch : ImperialVolume(), SystemScientificUnit<MeasurementSystem.Imperial, PhysicalQuantity.Volume> by Cubic(Inch) {
    override val symbol: String = "cu in"
}

@Serializable
object CubicFoot : ImperialVolume(), SystemScientificUnit<MeasurementSystem.Imperial, PhysicalQuantity.Volume> by Cubic(Foot) {
    override val symbol: String = "cu ft"
}

@Serializable
object CubicYard : ImperialVolume(), SystemScientificUnit<MeasurementSystem.Imperial, PhysicalQuantity.Volume> by Cubic(Yard) {
    override val symbol: String = "cu yd"
}

@Serializable
object CubicMile : ImperialVolume(), SystemScientificUnit<MeasurementSystem.Imperial, PhysicalQuantity.Volume> by Cubic(Mile) {
    override val symbol: String = "cu mi"
}

// US Imperial

@Serializable
data class USCustomaryImperialVolumeWrapper(val imperial: ImperialVolume) : USCustomaryVolume() {
    override val symbol: String = imperial.symbol
    override fun fromSIUnit(value: Decimal): Decimal = imperial.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = imperial.toSIUnit(value)
}

val <VolumeUnit : ImperialVolume> VolumeUnit.usCustomary get() = USCustomaryImperialVolumeWrapper(this)

@Serializable
object AcreFoot : USCustomaryVolume() {
    override val symbol: String = "ac ft"
    override fun toSIUnit(value: Decimal): Decimal = Foot.toSIUnit(Acre.toSIUnit(value))
    override fun fromSIUnit(value: Decimal): Decimal = Acre.fromSIUnit(Foot.fromSIUnit(value))
}

@Serializable
object AcreInch : USCustomaryVolume() {
    override val symbol: String = "ac in"
    override fun toSIUnit(value: Decimal): Decimal = Inch.toSIUnit(Acre.toSIUnit(value))
    override fun fromSIUnit(value: Decimal): Decimal = Acre.fromSIUnit(Inch.fromSIUnit(value))
}

@Serializable
object UsFluidDram : USCustomaryVolume() {
    override val symbol: String = "fl dr"
    private const val US_DRAMS_IN_FLUID_OUNCE = 8
    override fun toSIUnit(value: Decimal): Decimal = UsFluidOunce.toSIUnit(value / US_DRAMS_IN_FLUID_OUNCE.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = UsFluidOunce.fromSIUnit(value) * US_DRAMS_IN_FLUID_OUNCE.toDecimal()
}

@Serializable
object UsFluidOunce : USCustomaryVolume() {
    override val symbol: String = "fl oz"
    private const val US_FLUID_OUNCES_IN_GALLON = 128
    override fun toSIUnit(value: Decimal): Decimal = UsLiquidGallon.toSIUnit(value / US_FLUID_OUNCES_IN_GALLON.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = UsLiquidGallon.fromSIUnit(value) * US_FLUID_OUNCES_IN_GALLON.toDecimal()
}

@Serializable
object UsCustomaryCup : USCustomaryVolume() {
    override val symbol: String = "cup"
    private const val US_LEGAL_CUPS_IN_GALLON = 16
    override fun toSIUnit(value: Decimal): Decimal = UsLiquidGallon.toSIUnit(value / US_LEGAL_CUPS_IN_GALLON.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = UsLiquidGallon.fromSIUnit(value) * US_LEGAL_CUPS_IN_GALLON.toDecimal()
}

@Serializable
object UsLegalCup : USCustomaryVolume() {
    override val symbol: String = "cup"
    private const val MILLILITERS_IN_CUP = 240
    override fun toSIUnit(value: Decimal): Decimal = Milliliter.toSIUnit(value * MILLILITERS_IN_CUP.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = Milliliter.fromSIUnit(value) / MILLILITERS_IN_CUP.toDecimal()
}

@Serializable
object UsLiquidPint : USCustomaryVolume() {
    override val symbol: String = "pint"
    private const val US_PINTS_IN_GALLON = 8
    override fun toSIUnit(value: Decimal): Decimal = UsLiquidGallon.toSIUnit(value / US_PINTS_IN_GALLON.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = UsLiquidGallon.fromSIUnit(value) * US_PINTS_IN_GALLON.toDecimal()
}

@Serializable
object UsLiquidQuart : USCustomaryVolume() {
    override val symbol: String = "qt"
    private const val US_QUARTS_IN_GALLON = 4
    override fun toSIUnit(value: Decimal): Decimal = UsLiquidGallon.toSIUnit(value / US_QUARTS_IN_GALLON.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = UsLiquidGallon.fromSIUnit(value) * US_QUARTS_IN_GALLON.toDecimal()
}

@Serializable
object UsLiquidGallon : USCustomaryVolume() {
    override val symbol: String = "gal"
    private const val CUBIC_INCH_IN_GALLON = 231
    override fun toSIUnit(value: Decimal): Decimal = CubicInch.toSIUnit(value * CUBIC_INCH_IN_GALLON.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = CubicInch.fromSIUnit(value) / CUBIC_INCH_IN_GALLON.toDecimal()
}

// UK Imperial
@Serializable
data class UKImperialImperialVolumeWrapper(val imperial: ImperialVolume) : UKImperialVolume() {
    override val symbol: String = imperial.symbol
    override fun fromSIUnit(value: Decimal): Decimal = imperial.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = imperial.toSIUnit(value)
}

val <VolumeUnit : ImperialVolume> VolumeUnit.ukImperial get() = UKImperialImperialVolumeWrapper(this)

@Serializable
object ImperialFluidDram : UKImperialVolume() {
    override val symbol: String = "fl dr"
    private const val IMPERIAL_FLUID_DRAM_IN_FLUID_OUNCE = 8
    override fun toSIUnit(value: Decimal): Decimal = ImperialFluidOunce.toSIUnit(value / IMPERIAL_FLUID_DRAM_IN_FLUID_OUNCE.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = ImperialFluidOunce.fromSIUnit(value) * IMPERIAL_FLUID_DRAM_IN_FLUID_OUNCE.toDecimal()
}

@Serializable
object ImperialFluidOunce : UKImperialVolume() {
    override val symbol: String = "fl oz"
    private const val IMPERIAL_FLUID_OUNCES_GALLON = 160
    override fun toSIUnit(value: Decimal): Decimal = ImperialGallon.toSIUnit(value / IMPERIAL_FLUID_OUNCES_GALLON.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = ImperialGallon.fromSIUnit(value) * IMPERIAL_FLUID_OUNCES_GALLON.toDecimal()
}

@Serializable
object MetricCup : UKImperialVolume() {
    override val symbol: String = "cup"
    private const val MILLILITER_IN_CUP = 250
    override fun toSIUnit(value: Decimal): Decimal = Milliliter.toSIUnit(value * MILLILITER_IN_CUP.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = Milliliter.fromSIUnit(value) / MILLILITER_IN_CUP.toDecimal()
}

@Serializable
object ImperialPint : UKImperialVolume() {
    override val symbol: String = "pt"
    private const val IMPERIAL_PINTS_IN_GALLON = 8
    override fun toSIUnit(value: Decimal): Decimal = ImperialGallon.toSIUnit(value / IMPERIAL_PINTS_IN_GALLON.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = ImperialGallon.fromSIUnit(value) * IMPERIAL_PINTS_IN_GALLON.toDecimal()
}

@Serializable
object ImperialQuart : UKImperialVolume() {
    override val symbol: String = "qt"
    private const val IMPERIAL_QUARTS_IN_GALLON = 4
    override fun toSIUnit(value: Decimal): Decimal = ImperialGallon.toSIUnit(value / IMPERIAL_QUARTS_IN_GALLON.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = ImperialGallon.fromSIUnit(value) * IMPERIAL_QUARTS_IN_GALLON.toDecimal()
}

@Serializable
object ImperialGallon : UKImperialVolume() {
    override val symbol: String = "gal"
    private const val LITER_PER_GALLON = 4.54609
    override fun toSIUnit(value: Decimal): Decimal = Liter.toSIUnit(value * LITER_PER_GALLON.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = Liter.fromSIUnit(value) / LITER_PER_GALLON.toDecimal()
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricVolumetricFlowUnits: Set<MetricVolumetricFlow> get() = MetricVolumeUnits.flatMap { volume ->
    TimeUnits.map { volume per it }
}.toSet()

val ImperialVolumetricFlowUnits: Set<ImperialVolumetricFlow> get() = ImperialVolumeUnits.flatMap { volume ->
    TimeUnits.map { volume per it }
}.toSet()

val UKImperialVolumetricFlowUnits: Set<UKImperialVolumetricFlow> get() = UKImperialVolumeUnits.flatMap { volume ->
    TimeUnits.map { volume per it }
}.toSet()

val USCustomaryVolumetricFlowUnits: Set<USCustomaryVolumetricFlow> get() = USCustomaryVolumeUnits.flatMap { volume ->
    TimeUnits.map { volume per it }
}.toSet()

val VolumetricFlowUnits: Set<VolumetricFlow> get() = MetricVolumetricFlowUnits +
    ImperialVolumetricFlowUnits +
    UKImperialVolumetricFlowUnits.filter { it.volume !is UKImperialImperialVolumeWrapper }.toSet() +
    USCustomaryVolumetricFlowUnits.filter { it.volume !is USCustomaryImperialVolumeWrapper }.toSet()

@Serializable
sealed class VolumetricFlow : AbstractScientificUnit<PhysicalQuantity.VolumetricFlow>() {
    abstract val volume: Volume
    abstract val per: Time
    override val quantity = PhysicalQuantity.VolumetricFlow
    override val symbol: String by lazy { "${volume.symbol} / ${per.symbol}" }
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(volume.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = volume.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricVolumetricFlow(override val volume: MetricVolume, override val per: Time) : VolumetricFlow(), MetricScientificUnit<PhysicalQuantity.VolumetricFlow> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialVolumetricFlow(override val volume: ImperialVolume, override val per: Time) : VolumetricFlow(), ImperialScientificUnit<PhysicalQuantity.VolumetricFlow> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = UKImperialVolumetricFlow(volume.ukImperial, per)
    val usCustomary get() = USCustomaryVolumetricFlow(volume.usCustomary, per)
}
@Serializable
data class UKImperialVolumetricFlow(override val volume: UKImperialVolume, override val per: Time) : VolumetricFlow(), UKImperialScientificUnit<PhysicalQuantity.VolumetricFlow> {
    override val system = MeasurementSystem.UKImperial
}
@Serializable
data class USCustomaryVolumetricFlow(override val volume: USCustomaryVolume, override val per: Time) : VolumetricFlow(), USCustomaryScientificUnit<PhysicalQuantity.VolumetricFlow> {
    override val system = MeasurementSystem.USCustomary
}

infix fun MetricVolume.per(time: Time) = MetricVolumetricFlow(this, time)
infix fun ImperialVolume.per(time: Time) = ImperialVolumetricFlow(this, time)
infix fun UKImperialVolume.per(time: Time) = UKImperialVolumetricFlow(this, time)
infix fun USCustomaryVolume.per(time: Time) = USCustomaryVolumetricFlow(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricVolumetricFluxUnits: Set<MetricVolumetricFlux> get() = MetricVolumetricFlowUnits.flatMap { volumetricFlow ->
    MetricAreaUnits.map { volumetricFlow per it }
}.toSet()

val ImperialVolumetricFluxUnits: Set<ImperialVolumetricFlux> get() = ImperialVolumetricFlowUnits.flatMap { volumetricFlow ->
    ImperialAreaUnits.map { volumetricFlow per it }
}.toSet()

val UKImperialVolumetricFluxUnits: Set<UKImperialVolumetricFlux> get() = UKImperialVolumetricFlowUnits.flatMap { volumetricFlow ->
    ImperialAreaUnits.map { volumetricFlow per it }
}.toSet()

val USCustomaryVolumetricFluxUnits: Set<USCustomaryVolumetricFlux> get() = USCustomaryVolumetricFlowUnits.flatMap { volumetricFlow ->
    ImperialAreaUnits.map { volumetricFlow per it }
}.toSet()

val VolumetricFluxUnits: Set<VolumetricFlux> get() = MetricVolumetricFluxUnits +
    ImperialVolumetricFluxUnits +
    UKImperialVolumetricFluxUnits.filter { it.volumetricFlow.volume !is UKImperialImperialVolumeWrapper }.toSet() +
    USCustomaryVolumetricFluxUnits.filter { it.volumetricFlow.volume !is USCustomaryImperialVolumeWrapper }.toSet()

@Serializable
sealed class VolumetricFlux : AbstractScientificUnit<PhysicalQuantity.VolumetricFlux>() {
    abstract val volumetricFlow: VolumetricFlow
    abstract val per: Area
    override val quantity = PhysicalQuantity.VolumetricFlux
    override val symbol: String by lazy { "${volumetricFlow.symbol}⋅${per.symbol}" }
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(volumetricFlow.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = volumetricFlow.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricVolumetricFlux(override val volumetricFlow: MetricVolumetricFlow, override val per: MetricArea) : VolumetricFlux(), MetricScientificUnit<PhysicalQuantity.VolumetricFlux> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialVolumetricFlux(override val volumetricFlow: ImperialVolumetricFlow, override val per: ImperialArea) : VolumetricFlux(), ImperialScientificUnit<PhysicalQuantity.VolumetricFlux> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = volumetricFlow.ukImperial per per
    val usCustomary get() = volumetricFlow.usCustomary per per
}
@Serializable
data class UKImperialVolumetricFlux(override val volumetricFlow: UKImperialVolumetricFlow, override val per: ImperialArea) : VolumetricFlux(), UKImperialScientificUnit<PhysicalQuantity.VolumetricFlux> {
    override val system = MeasurementSystem.UKImperial
}
@Serializable
data class USCustomaryVolumetricFlux(override val volumetricFlow: USCustomaryVolumetricFlow, override val per: ImperialArea) : VolumetricFlux(), USCustomaryScientificUnit<PhysicalQuantity.VolumetricFlux> {
    override val system = MeasurementSystem.USCustomary
}

infix fun MetricVolumetricFlow.per(area: MetricArea) = MetricVolumetricFlux(this, area)
infix fun ImperialVolumetricFlow.per(area: ImperialArea) = ImperialVolumetricFlux(this, area)
infix fun UKImperialVolumetricFlow.per(area: ImperialArea) = UKImperialVolumetricFlux(this, area)
infix fun USCustomaryVolumetricFlow.per(area: ImperialArea) = USCustomaryVolumetricFlux(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricWeightUnits: Set<MetricWeight> get() = setOf(
    Kilogram,
    Nanogram,
    Microgram,
    Milligram,
    Centigram,
    Decigram,
    Gram,
    Decagram,
    Hectogram,
    Megagram,
    Gigagram,
    Tonne,
    Dalton,
    Nanodalton,
    Microdalton,
    Millidalton,
    Centidalton,
    Decidalton,
    Decadalton,
    HectoDalton,
    Kilodalton,
    Megadalton,
    Gigadalton
)

val ImperialWeightUnits: Set<ImperialWeight> get() = setOf(
    Grain,
    Ounce,
    Pound,
    Stone,
    Slug
)

val UKImperialWeightUnits: Set<UKImperialWeight> get() = ImperialWeightUnits.map { it.ukImperial }.toSet() + setOf(ImperialTon)
val USCustomaryWeightUnits: Set<USCustomaryWeight> get() = ImperialWeightUnits.map { it.usCustomary }.toSet() + setOf(UsTon)

val WeightUnits: Set<Weight> get() = MetricWeightUnits +
    ImperialWeightUnits +
    UKImperialWeightUnits.filter { it !is UKImperialImperialWeightWrapper }.toSet() +
    USCustomaryWeightUnits.filter { it !is USCustomaryImperialWeightWrapper }.toSet()

@Serializable
sealed class Weight : AbstractScientificUnit<PhysicalQuantity.Weight>()

@Serializable
sealed class MetricWeight : Weight(), MetricScientificUnit<PhysicalQuantity.Weight>

@Serializable
sealed class ImperialWeight(override val symbol: String) : Weight(), ImperialScientificUnit<PhysicalQuantity.Weight> {
    override val system = MeasurementSystem.Imperial
    override val quantity = PhysicalQuantity.Weight
}

@Serializable
sealed class USCustomaryWeight(override val symbol: String) : Weight(), USCustomaryScientificUnit<PhysicalQuantity.Weight> {
    override val system = MeasurementSystem.USCustomary
    override val quantity = PhysicalQuantity.Weight
}

@Serializable
sealed class UKImperialWeight(override val symbol: String) : Weight(), UKImperialScientificUnit<PhysicalQuantity.Weight> {
    override val system = MeasurementSystem.UKImperial
    override val quantity = PhysicalQuantity.Weight
}

// Metric Weight

@Serializable
object Gram : MetricWeight(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight> {
    override val symbol: String = "g"
    private const val GRAMS_IN_KILOGRAM = 1000.0
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Weight
    override fun toSIUnit(value: Decimal): Decimal = value / GRAMS_IN_KILOGRAM.toDecimal()
    override fun fromSIUnit(value: Decimal): Decimal = value * GRAMS_IN_KILOGRAM.toDecimal()
}

@Serializable
object Nanogram : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Gram> by Nano(Gram)
@Serializable
object Microgram : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Gram> by Micro(Gram)
@Serializable
object Milligram : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Gram> by Milli(Gram)
@Serializable
object Centigram : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Gram> by Centi(Gram)
@Serializable
object Decigram : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Gram> by Deci(Gram)
@Serializable
object Decagram : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Gram> by Deca(Gram)
@Serializable
object Hectogram : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Gram> by Hecto(Gram)
@Serializable
object Kilogram : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Gram> by Kilo(Gram)
@Serializable
object Megagram : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Gram> by Mega(Gram)
@Serializable
object Gigagram : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Gram> by Giga(Gram)

@Serializable
object Tonne : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Gram> by Mega(Gram) {
    override val symbol: String = "t"
}

@Serializable
object Dalton : MetricWeight(), MetricBaseUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight> {
    override val symbol: String = "Da"
    override val system = MeasurementSystem.Metric
    override val quantity = PhysicalQuantity.Weight
    override fun toSIUnit(value: Decimal): Decimal = Gram.toSIUnit(value) / AvogadroConstant
    override fun fromSIUnit(value: Decimal): Decimal = Gram.fromSIUnit(value) * AvogadroConstant
}

@Serializable
object Nanodalton : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Dalton> by Nano(Dalton)
@Serializable
object Microdalton : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Dalton> by Micro(Dalton)
@Serializable
object Millidalton : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Dalton> by Milli(Dalton)
@Serializable
object Centidalton : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Dalton> by Centi(Dalton)
@Serializable
object Decidalton : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Dalton> by Deci(Dalton)
@Serializable
object Decadalton : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Dalton> by Deca(Dalton)
@Serializable
object HectoDalton : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Dalton> by Hecto(Dalton)
@Serializable
object Kilodalton : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Dalton> by Kilo(Dalton)
@Serializable
object Megadalton : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Dalton> by Mega(Dalton)
@Serializable
object Gigadalton : MetricWeight(), MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Weight, Dalton> by Giga(Dalton)

// Imperial Weight
@Serializable
object Grain : ImperialWeight("gr") {
    private const val GRAIN_IN_POUND = 7000
    override fun toSIUnit(value: Decimal): Decimal = Pound.toSIUnit(value / GRAIN_IN_POUND.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = Pound.fromSIUnit(value) * GRAIN_IN_POUND.toDecimal()
}

@Serializable
object Dram : ImperialWeight("dr") {
    private const val DRAMS_IN_POUND = 256
    override fun toSIUnit(value: Decimal): Decimal = Pound.toSIUnit(value / DRAMS_IN_POUND.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = Pound.fromSIUnit(value) * DRAMS_IN_POUND.toDecimal()
}

@Serializable
object Ounce : ImperialWeight("oz") {
    private const val OUNCES_IN_POUND = 16
    override fun toSIUnit(value: Decimal): Decimal = Pound.toSIUnit(value / OUNCES_IN_POUND.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = Pound.fromSIUnit(value) * OUNCES_IN_POUND.toDecimal()
}

@Serializable
object Pound : ImperialWeight("lb") {
    private const val KILOGRAM_IN_POUND = 0.45359237
    override fun toSIUnit(value: Decimal): Decimal = value * KILOGRAM_IN_POUND.toDecimal()
    override fun fromSIUnit(value: Decimal): Decimal = value / KILOGRAM_IN_POUND.toDecimal()
}

@Serializable
object Stone : ImperialWeight("st") {
    private const val STONES_IN_POUND = 14
    override fun toSIUnit(value: Decimal): Decimal = Pound.toSIUnit(value * STONES_IN_POUND.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = Pound.fromSIUnit(value) / STONES_IN_POUND.toDecimal()
}

@Serializable
object Slug : ImperialWeight("slug") {
    override fun toSIUnit(value: Decimal): Decimal = PoundForce.toSIUnit((Foot per Second).fromSIUnit(value))
    override fun fromSIUnit(value: Decimal): Decimal = (Foot per Second).toSIUnit(PoundForce.fromSIUnit(value))
}

// also long ton
@Serializable
object ImperialTon : UKImperialWeight("ton") {
    private const val POUND_IN_LONG_TONES = 2240
    override fun toSIUnit(value: Decimal): Decimal = Pound.toSIUnit(value * POUND_IN_LONG_TONES.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = Pound.fromSIUnit(value) / POUND_IN_LONG_TONES.toDecimal()
}

@Serializable
data class UKImperialImperialWeightWrapper(val imperial: ImperialWeight) : UKImperialWeight(imperial.symbol) {
    override fun fromSIUnit(value: Decimal): Decimal = imperial.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = imperial.toSIUnit(value)
}

val <WeightUnit : ImperialWeight> WeightUnit.ukImperial get() = UKImperialImperialWeightWrapper(this)

// also short ton
@Serializable
object UsTon : USCustomaryWeight("ton") {
    private const val POUND_IN_SHORT_TONES = 2000
    override fun toSIUnit(value: Decimal): Decimal = Pound.toSIUnit(value * POUND_IN_SHORT_TONES.toDecimal())
    override fun fromSIUnit(value: Decimal): Decimal = Pound.fromSIUnit(value) / POUND_IN_SHORT_TONES.toDecimal()
}

@Serializable
data class USCustomaryImperialWeightWrapper(val imperial: ImperialWeight) : USCustomaryWeight(imperial.symbol) {
    override fun fromSIUnit(value: Decimal): Decimal = imperial.fromSIUnit(value)
    override fun toSIUnit(value: Decimal): Decimal = imperial.toSIUnit(value)
}

val <WeightUnit : ImperialWeight> WeightUnit.usCustomary get() = USCustomaryImperialWeightWrapper(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.unit

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import kotlinx.serialization.Serializable

val MetricYankUnits: Set<MetricYank> get() = MetricForceUnits.flatMap { force ->
    TimeUnits.map { force per it }
}.toSet()

val ImperialYankUnits: Set<ImperialYank> get() = ImperialForceUnits.flatMap { force ->
    TimeUnits.map { force per it }
}.toSet()

val UKImperialYankUnits: Set<UKImperialYank> get() = UKImperialForceUnits.flatMap { force ->
    TimeUnits.map { force per it }
}.toSet()

val USCustomaryYankUnits: Set<USCustomaryYank> get() = USCustomaryForceUnits.flatMap { force ->
    TimeUnits.map { force per it }
}.toSet()

val YankUnits: Set<Yank> get() = MetricYankUnits +
    ImperialYankUnits +
    UKImperialYankUnits.filter { it.force !is UKImperialImperialForceWrapper }.toSet() +
    USCustomaryYankUnits.filter { it.force !is USCustomaryImperialForceWrapper }.toSet()

@Serializable
sealed class Yank : AbstractScientificUnit<PhysicalQuantity.Yank>() {
    abstract val force: Force
    abstract val per: Time
    override val quantity = PhysicalQuantity.Yank
    override val symbol: String by lazy { "${force.symbol} / ${per.symbol}" }
    override fun fromSIUnit(value: Decimal): Decimal = per.toSIUnit(force.fromSIUnit(value))
    override fun toSIUnit(value: Decimal): Decimal = force.toSIUnit(per.fromSIUnit(value))
}

@Serializable
data class MetricYank(override val force: MetricForce, override val per: Time) : Yank(), MetricScientificUnit<PhysicalQuantity.Yank> {
    override val system = MeasurementSystem.Metric
}
@Serializable
data class ImperialYank(override val force: ImperialForce, override val per: Time) : Yank(), ImperialScientificUnit<PhysicalQuantity.Yank> {
    override val system = MeasurementSystem.Imperial
    val ukImperial get() = UKImperialYank(force.ukImperial, per)
    val usCustomary get() = USCustomaryYank(force.usCustomary, per)
}
@Serializable
data class UKImperialYank(override val force: UKImperialForce, override val per: Time) : Yank(), UKImperialScientificUnit<PhysicalQuantity.Yank> {
    override val system = MeasurementSystem.UKImperial
}
@Serializable
data class USCustomaryYank(override val force: USCustomaryForce, override val per: Time) : Yank(), USCustomaryScientificUnit<PhysicalQuantity.Yank> {
    override val system = MeasurementSystem.USCustomary
}

infix fun MetricForce.per(time: Time) = MetricYank(this, time)
infix fun ImperialForce.per(time: Time) = ImperialYank(this, time)
infix fun UKImperialForce.per(time: Time) = UKImperialYank(this, time)
infix fun USCustomaryForce.per(time: Time) = USCustomaryYank(this, time)

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.architecture

import co.touchlab.stately.ensureNeverFrozen
import com.splendo.kaluga.architecture.viewmodel.ViewModel
import com.splendo.kaluga.test.BaseTest
import com.splendo.kaluga.test.UIThreadTest
import kotlinx.coroutines.CoroutineScope
import kotlin.test.BeforeTest

abstract class ViewModelTest<VM : ViewModel>(allowFreezing: Boolean = false) : BaseTest() {

    init {
        if (!allowFreezing) ensureNeverFrozen()
    }

    lateinit var viewModel: VM

    protected abstract fun createViewModel(): VM

    @BeforeTest
    override fun beforeTest() {
        super.beforeTest()
        viewModel = createViewModel()
    }
}

abstract class SimpleUIThreadViewModelTest<VM : ViewModel> :
    UIThreadViewModelTest<UIThreadViewModelTest.ViewModelTestContext<VM>, VM>(allowFreezing = true) {

    override val createTestContext: suspend (CoroutineScope) -> ViewModelTestContext<VM> =
        { LazyViewModelTestContext(it, ::createViewModel) }

    abstract fun createViewModel(): VM
}

abstract class UIThreadViewModelTest<VMC : UIThreadViewModelTest.ViewModelTestContext<VM>, VM : ViewModel>(allowFreezing: Boolean = false) :
    UIThreadTest<VMC>(allowFreezing) {

    open class LazyViewModelTestContext<VM>(coroutineScope: CoroutineScope, private val createViewModel: () -> VM) :
        ViewModelTestContext<VM>, CoroutineScope by coroutineScope {
        override val viewModel: VM by lazy { createViewModel() }
    }

    interface ViewModelTestContext<VM> : TestContext {
        val viewModel: VM
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.koin

import com.splendo.kaluga.test.UIThreadTest
import org.koin.core.component.KoinComponent
import org.koin.core.context.loadKoinModules
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.core.module.Module
import org.koin.dsl.KoinAppDeclaration

abstract class KoinUIThreadTest<TC : KoinUIThreadTest.KoinTestContext>(allowFreezing: Boolean = false) : UIThreadTest<TC>(allowFreezing) {

    open class KoinTestContext(
        appDeclaration: KoinAppDeclaration? = null,
        koinModules: List<Module>
    ) :
        TestContext, KoinComponent {

        constructor(vararg koinModules: Module) : this(null, koinModules.toList())
        constructor(appDeclaration: KoinAppDeclaration, vararg koinModules: Module) : this(
            appDeclaration,
            koinModules.toList()
        )

        init {
            startKoin {
                appDeclaration?.invoke(this)
                modules(koinModules)
            }
            loadKoinModules(koinModules.toList())
        }

        override fun dispose() {
            stopKoin()
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.utils

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.

*/

import ru.pocketbyte.kydra.log.KydraLog

class KotlinNativeFramework {

    fun hello() = com.splendo.kaluga.example.shared.helloCommon()

    // expose a dependency to Swift as an example
    fun logger(): ru.pocketbyte.kydra.log.Logger = KydraLog.logger
}

package com.splendo.kaluga.example.shared.stylable

import com.splendo.kaluga.resources.DefaultColors
import com.splendo.kaluga.resources.defaultFont
import com.splendo.kaluga.resources.stylable.BackgroundStyle
import com.splendo.kaluga.resources.stylable.ButtonStateStyle
import com.splendo.kaluga.resources.stylable.ButtonStyle
import com.splendo.kaluga.resources.stylable.GradientStyle

object ButtonStyles {

    val textButton by lazy {
        ButtonStyle(TextStyles.redText)
    }
    val redButton by lazy {
        ButtonStyle(
            TextStyles.whiteText,
            backgroundColor = DefaultColors.red,
            pressedBackgroundColor = DefaultColors.maroon,
            disabledBackgroundColor = DefaultColors.lightGray
        )
    }
    val roundedButton by lazy {
        ButtonStyle(
            defaultFont,
            12.0f,
            defaultStyle = ButtonStateStyle(DefaultColors.white, DefaultColors.deepSkyBlue, BackgroundStyle.Shape.Rectangle(10.0f, setOf(BackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT, BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_LEFT))),
            pressedStyle = ButtonStateStyle(DefaultColors.azure, DefaultColors.lightSkyBlue, BackgroundStyle.Shape.Rectangle(5.0f, setOf(BackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT, BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_RIGHT))),
            disabledStyle = ButtonStateStyle(DefaultColors.black, DefaultColors.lightGray, BackgroundStyle.Shape.Rectangle(10.0f))
        )
    }
    val ovalButton by lazy {
        ButtonStyle(
            defaultFont,
            12.0f,
            defaultStyle = ButtonStateStyle(DefaultColors.white, DefaultColors.deepSkyBlue, BackgroundStyle.Shape.Oval),
            pressedStyle = ButtonStateStyle(DefaultColors.azure, DefaultColors.lightSkyBlue, BackgroundStyle.Shape.Oval),
            disabledStyle = ButtonStateStyle(DefaultColors.black, DefaultColors.lightGray, BackgroundStyle.Shape.Oval)
        )
    }
    val redButtonWithStroke by lazy {
        ButtonStyle(
            defaultFont,
            12.0f,
            defaultStyle = ButtonStateStyle(
                DefaultColors.white,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Solid(DefaultColors.red),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white),
                    BackgroundStyle.Shape.Rectangle()
                )
            ),
            pressedStyle = ButtonStateStyle(
                DefaultColors.white,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Solid(DefaultColors.maroon),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.mistyRose),
                    BackgroundStyle.Shape.Rectangle()
                )
            ),
            disabledStyle = ButtonStateStyle(
                DefaultColors.black,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Solid(DefaultColors.lightGray),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black),
                    BackgroundStyle.Shape.Rectangle()
                )
            )
        )
    }
    val roundedButtonWithStroke by lazy {
        ButtonStyle(
            defaultFont,
            12.0f,
            defaultStyle = ButtonStateStyle(
                DefaultColors.white,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Solid(DefaultColors.deepSkyBlue),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white),
                    BackgroundStyle.Shape.Rectangle(10.0f, setOf(BackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT, BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_LEFT))
                )
            ),
            pressedStyle = ButtonStateStyle(
                DefaultColors.azure,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Solid(DefaultColors.lightSkyBlue),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.azure),
                    BackgroundStyle.Shape.Rectangle(5.0f, setOf(BackgroundStyle.Shape.Rectangle.Corner.TOP_LEFT, BackgroundStyle.Shape.Rectangle.Corner.BOTTOM_RIGHT))
                )
            ),
            disabledStyle = ButtonStateStyle(
                DefaultColors.black,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Solid(DefaultColors.lightGray),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black),
                    BackgroundStyle.Shape.Rectangle(10.0f)
                )
            )
        )
    }
    val ovalButtonWithStroke by lazy {
        ButtonStyle(
            defaultFont,
            12.0f,
            defaultStyle = ButtonStateStyle(
                DefaultColors.white,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Solid(DefaultColors.deepSkyBlue),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white),
                    BackgroundStyle.Shape.Oval
                )
            ),
            pressedStyle = ButtonStateStyle(
                DefaultColors.azure,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Solid(DefaultColors.lightSkyBlue),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.azure),
                    BackgroundStyle.Shape.Oval
                )
            ),
            disabledStyle = ButtonStateStyle(
                DefaultColors.black,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Solid(DefaultColors.lightGray),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black),
                    BackgroundStyle.Shape.Oval
                )
            )
        )
    }

    val linearGradientButton by lazy {
        ButtonStyle(
            defaultFont,
            12.0f,
            defaultStyle = ButtonStateStyle(
                DefaultColors.white,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Gradient(GradientStyle.Linear(listOf(DefaultColors.deepSkyBlue, DefaultColors.lightSkyBlue), GradientStyle.Linear.Orientation.LEFT_RIGHT)),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white),
                    BackgroundStyle.Shape.Rectangle()
                )
            ),
            pressedStyle = ButtonStateStyle(
                DefaultColors.azure,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Gradient(GradientStyle.Linear(listOf(DefaultColors.midnightBlue, DefaultColors.deepSkyBlue), GradientStyle.Linear.Orientation.LEFT_RIGHT)),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.mistyRose),
                    BackgroundStyle.Shape.Rectangle()
                )
            ),
            disabledStyle = ButtonStateStyle(
                DefaultColors.black,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Gradient(GradientStyle.Linear(listOf(DefaultColors.lightGray, DefaultColors.gray), GradientStyle.Linear.Orientation.LEFT_RIGHT)),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black),
                    BackgroundStyle.Shape.Rectangle()
                )
            )
        )
    }

    val radialGradientButton by lazy {
        ButtonStyle(
            defaultFont,
            12.0f,
            defaultStyle = ButtonStateStyle(
                DefaultColors.white,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Gradient(GradientStyle.Radial(listOf(DefaultColors.deepSkyBlue, DefaultColors.lightSkyBlue), 50.0f, GradientStyle.CenterPoint(0.3f, 0.3f))),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white),
                    BackgroundStyle.Shape.Rectangle()
                )
            ),
            pressedStyle = ButtonStateStyle(
                DefaultColors.azure,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Gradient(GradientStyle.Radial(listOf(DefaultColors.midnightBlue, DefaultColors.deepSkyBlue), 25.0f, GradientStyle.CenterPoint(0.6f, 0.6f))),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.mistyRose),
                    BackgroundStyle.Shape.Rectangle()
                )
            ),
            disabledStyle = ButtonStateStyle(
                DefaultColors.black,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Gradient(GradientStyle.Radial(listOf(DefaultColors.lightGray, DefaultColors.gray), 50.0f)),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black),
                    BackgroundStyle.Shape.Rectangle()
                )
            )
        )
    }

    val angularGradientButton by lazy {
        ButtonStyle(
            defaultFont,
            12.0f,
            defaultStyle = ButtonStateStyle(
                DefaultColors.white,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Gradient(GradientStyle.Angular(listOf(DefaultColors.deepSkyBlue, DefaultColors.lightSkyBlue), GradientStyle.CenterPoint(0.3f, 0.3f))),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.white),
                    BackgroundStyle.Shape.Rectangle()
                )
            ),
            pressedStyle = ButtonStateStyle(
                DefaultColors.azure,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Gradient(GradientStyle.Angular(listOf(DefaultColors.midnightBlue, DefaultColors.deepSkyBlue), GradientStyle.CenterPoint(0.6f, 0.6f))),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.mistyRose),
                    BackgroundStyle.Shape.Rectangle()
                )
            ),
            disabledStyle = ButtonStateStyle(
                DefaultColors.black,
                BackgroundStyle(
                    BackgroundStyle.FillStyle.Gradient(GradientStyle.Angular(listOf(DefaultColors.lightGray, DefaultColors.gray))),
                    BackgroundStyle.StrokeStyle.Stroke(2.0f, DefaultColors.black),
                    BackgroundStyle.Shape.Rectangle()
                )
            )
        )
    }
}

package com.splendo.kaluga.example.shared.stylable

import com.splendo.kaluga.resources.DefaultColors
import com.splendo.kaluga.resources.defaultBoldFont
import com.splendo.kaluga.resources.defaultFont
import com.splendo.kaluga.resources.defaultItalicFont
import com.splendo.kaluga.resources.defaultMonospaceFont
import com.splendo.kaluga.resources.stylable.TextAlignment
import com.splendo.kaluga.resources.stylable.TextStyle

object TextStyles {

    val defaultText by lazy {
        TextStyle(defaultFont, DefaultColors.dimGray, 12.0f)
    }
    val defaultTitle by lazy {
        TextStyle(defaultFont, DefaultColors.dimGray, 16.0f)
    }
    val defaultBoldText by lazy {
        TextStyle(defaultBoldFont, DefaultColors.dimGray, 12.0f)
    }
    val defaultItalicText by lazy {
        TextStyle(defaultItalicFont, DefaultColors.dimGray, 12.0f)
    }
    val defaultMonospaceText by lazy {
        TextStyle(defaultMonospaceFont, DefaultColors.dimGray, 12.0f)
    }
    val whiteText by lazy {
        TextStyle(defaultFont, DefaultColors.white, 12.0f)
    }
    val redText by lazy {
        TextStyle(defaultFont, DefaultColors.red, 12.0f)
    }
    val oppositeText by lazy {
        TextStyle(defaultFont, DefaultColors.dimGray, 12.0f, TextAlignment.END)
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel

import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.observable.observableOf
import com.splendo.kaluga.architecture.observable.toInitializedSubject
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch

sealed class ExampleTabNavigation : NavigationAction<Nothing>(null) {

    object FeatureList : ExampleTabNavigation()
    object Info : ExampleTabNavigation()
}

class ExampleViewModel(navigator: Navigator<ExampleTabNavigation>) : NavigatingViewModel<ExampleTabNavigation>(navigator) {

    sealed class Tab(val title: String) {
        object FeatureList : Tab("Features")
        object Info : Tab("About")
    }

    val tabs = observableOf(listOf(Tab.FeatureList, Tab.Info))

    private val _tab = MutableStateFlow<Tab>(Tab.FeatureList)
    val tab = _tab.toInitializedSubject(coroutineScope)

    override fun onResume(scope: CoroutineScope) {
        super.onResume(scope)
        scope.launch(Dispatchers.Main.immediate) {
            _tab.collect { currentTab ->
                navigator.navigate(
                    when (currentTab) {
                        is Tab.FeatureList -> ExampleTabNavigation.FeatureList
                        is Tab.Info -> ExampleTabNavigation.Info
                    }
                )
            }
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.acceleration

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("accelerationFromForceAndMassDefault")
fun <
    MassUnit : Weight,
    AccelerationUnit : Acceleration,
    ForceUnit : Force
    > AccelerationUnit.acceleration(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    mass: ScientificValue<PhysicalQuantity.Weight, MassUnit>
) = acceleration(force, mass, ::DefaultScientificValue)

@JvmName("accelerationFromForceAndMass")
fun <
    MassUnit : Weight,
    AccelerationUnit : Acceleration,
    ForceUnit : Force,
    Value : ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>
    > AccelerationUnit.acceleration(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    mass: ScientificValue<PhysicalQuantity.Weight, MassUnit>,
    factory: (Decimal, AccelerationUnit) -> Value
) = byDividing(force, mass, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.acceleration

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.Jolt
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("accelerationFromJoltAndTimeDefault")
fun <
    JoltUnit : Jolt,
    TimeUnit : Time,
    AccelerationUnit : Acceleration
    > AccelerationUnit.acceleration(
    jolt: ScientificValue<PhysicalQuantity.Jolt, JoltUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = acceleration(jolt, time, ::DefaultScientificValue)

@JvmName("accelerationFromJoltAndTime")
fun <
    JoltUnit : Jolt,
    TimeUnit : Time,
    AccelerationUnit : Acceleration,
    Value : ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>
    > AccelerationUnit.acceleration(
    jolt: ScientificValue<PhysicalQuantity.Jolt, JoltUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, AccelerationUnit) -> Value
) = byMultiplying(jolt, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.acceleration

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("accelerationFromSpeedAndTimeDefault")
fun <
    SpeedUnit : Speed,
    TimeUnit : Time,
    AccelerationUnit : Acceleration
    > AccelerationUnit.acceleration(
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = acceleration(speed, time, ::DefaultScientificValue)

@JvmName("accelerationFromSpeedAndTime")
fun <
    SpeedUnit : Speed,
    TimeUnit : Time,
    AccelerationUnit : Acceleration,
    Value : ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>
    > AccelerationUnit.acceleration(
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, AccelerationUnit) -> Value
) = byDividing(speed, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.acceleration

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.force.force
import com.splendo.kaluga.scientific.converter.weight.times
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.Grain
import com.splendo.kaluga.scientific.unit.GrainForce
import com.splendo.kaluga.scientific.unit.Gram
import com.splendo.kaluga.scientific.unit.ImperialAcceleration
import com.splendo.kaluga.scientific.unit.ImperialTon
import com.splendo.kaluga.scientific.unit.ImperialTonForce
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.MetricAcceleration
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.Ounce
import com.splendo.kaluga.scientific.unit.OunceForce
import com.splendo.kaluga.scientific.unit.Pound
import com.splendo.kaluga.scientific.unit.PoundForce
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.UsTon
import com.splendo.kaluga.scientific.unit.UsTonForce
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("accelerationTimesGram")
infix operator fun ScientificValue<PhysicalQuantity.Acceleration, MetricAcceleration>.times(mass: ScientificValue<PhysicalQuantity.Weight, Gram>) =
    mass * this

@JvmName("metricAccelerationTimesMetricWeight")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Acceleration, MetricAcceleration>.times(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = mass * this

@JvmName("accelerationTimesPound")
infix operator fun ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>.times(mass: ScientificValue<PhysicalQuantity.Weight, Pound>) =
    PoundForce.force(mass, this)

@JvmName("accelerationTimesOunce")
infix operator fun ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>.times(mass: ScientificValue<PhysicalQuantity.Weight, Ounce>) =
    OunceForce.force(mass, this)

@JvmName("accelerationTimesGrain")
infix operator fun ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>.times(mass: ScientificValue<PhysicalQuantity.Weight, Grain>) =
    GrainForce.force(mass, this)

@JvmName("accelerationTimesUSTon")
infix operator fun ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>.times(mass: ScientificValue<PhysicalQuantity.Weight, UsTon>) =
    UsTonForce.force(mass, this)

@JvmName("accelerationTimesImperialTon")
infix operator fun ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>.times(mass: ScientificValue<PhysicalQuantity.Weight, ImperialTon>) =
    ImperialTonForce.force(mass, this)

@JvmName("imperialAccelerationTimesImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>.times(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = mass * this

@JvmName("imperialAccelerationTimesUKImperialWeight")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>.times(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = mass * this

@JvmName("imperialAccelerationTimesUSCustomaryWeight")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>.times(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = mass * this

@JvmName("accelerationTimesWeight")
infix operator fun <WeightUnit : Weight, AccelerationUnit : Acceleration> ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>.times(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = mass * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.acceleration

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.jolt.jolt
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.ImperialAcceleration
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricAcceleration
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricAccelerationDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Acceleration, MetricAcceleration>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).jolt(this, time)

@JvmName("imperialAccelerationDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).jolt(this, time)

@JvmName("speedDivTime")
infix operator fun <AccelerationUnit : Acceleration, TimeUnit : Time> ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (Meter per Second per Second per time.unit).jolt(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.acceleration

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.speed.speed
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.ImperialAcceleration
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricAcceleration
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricAccelerationTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Acceleration, MetricAcceleration>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.speed.speed(this, time)

@JvmName("imperialAccelerationTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.speed.speed(this, time)

@JvmName("speedTimesTime")
infix operator fun <AccelerationUnit : Acceleration, TimeUnit : Time> ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (Meter per Second).speed(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.acceleration

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.Jolt
import kotlin.jvm.JvmName

@JvmName("speedDivAcceleration")
infix operator fun <AccelerationUnit : Acceleration, JoltUnit : Jolt> ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>.div(
    jolt: ScientificValue<PhysicalQuantity.Jolt, JoltUnit>
) = jolt.unit.per.time(this, jolt)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.action

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Action
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("actionFromEnergyAndTimeDefault")
fun <
    ActionUnit : Action,
    TimeUnit : Time,
    EnergyUnit : Energy
    > ActionUnit.action(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = action(energy, time, ::DefaultScientificValue)

@JvmName("actionFromEnergyAndTime")
fun <
    ActionUnit : Action,
    TimeUnit : Time,
    EnergyUnit : Energy,
    Value : ScientificValue<PhysicalQuantity.Action, ActionUnit>
    > ActionUnit.action(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, ActionUnit) -> Value
) = byMultiplying(energy, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.action

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.energy.energy
import com.splendo.kaluga.scientific.unit.Action
import com.splendo.kaluga.scientific.unit.ImperialAction
import com.splendo.kaluga.scientific.unit.MetricAction
import com.splendo.kaluga.scientific.unit.MetricAndImperialAction
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("metricAndImperialActionDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Action, MetricAndImperialAction>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit.energy).energy(this, time)

@JvmName("metricActionDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Action, MetricAction>.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    (unit.energy).energy(this, time)

@JvmName("imperialActionDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Action, ImperialAction>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit.energy).energy(this, time)

@JvmName("actionDivTime")
infix operator fun <ActionUnit : Action, TimeUnit : Time> ScientificValue<PhysicalQuantity.Action, ActionUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit.energy).energy(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.action

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.Action
import com.splendo.kaluga.scientific.unit.Energy
import kotlin.jvm.JvmName

@JvmName("actionDivEnergy")
infix operator fun <ActionUnit : Action, EnergyUnit : Energy> ScientificValue<PhysicalQuantity.Action, ActionUnit>.div(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
) = unit.time.time(this, energy)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.amountOfSubstance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.CatalysticActivity
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("amountOfSubstanceFromCatalysisAndTimeDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    TimeUnit : Time,
    CatalysisUnit : CatalysticActivity
    > AmountOfSubstanceUnit.amountOfSubstance(
    catalysis: ScientificValue<PhysicalQuantity.CatalysticActivity, CatalysisUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = amountOfSubstance(catalysis, time, ::DefaultScientificValue)

@JvmName("amountOfSubstanceFromCatalysisAndTime")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    TimeUnit : Time,
    CatalysisUnit : CatalysticActivity,
    Value : ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
    > AmountOfSubstanceUnit.amountOfSubstance(
    catalysis: ScientificValue<PhysicalQuantity.CatalysticActivity, CatalysisUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, AmountOfSubstanceUnit) -> Value
) = byMultiplying(catalysis, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.amountOfSubstance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.MolarEnergy
import kotlin.jvm.JvmName

@JvmName("amountOfSubstanceFromEnergyAndMolarEnergyDefault")
fun <
    EnergyUnit : Energy,
    AmountOfSubstanceUnit : AmountOfSubstance,
    MolarEnergyUnit : MolarEnergy
    > AmountOfSubstanceUnit.amountOfSubstance(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>
) = amountOfSubstance(energy, molarEnergy, ::DefaultScientificValue)

@JvmName("amountOfSubstanceFromEnergyAndMolarEnergy")
fun <
    EnergyUnit : Energy,
    AmountOfSubstanceUnit : AmountOfSubstance,
    MolarEnergyUnit : MolarEnergy,
    Value : ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
    > AmountOfSubstanceUnit.amountOfSubstance(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>,
    factory: (Decimal, AmountOfSubstanceUnit) -> Value
) = byDividing(energy, molarEnergy, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.amountOfSubstance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("amountOfSubstanceFromMolalityAndWeightDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    WeightUnit : Weight,
    MolalityUnit : Molality
    > AmountOfSubstanceUnit.amountOfSubstance(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = amountOfSubstance(molality, weight, ::DefaultScientificValue)

@JvmName("amountOfSubstanceFromMolalityAndWeight")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    WeightUnit : Weight,
    MolalityUnit : Molality,
    Value : ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
    > AmountOfSubstanceUnit.amountOfSubstance(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    factory: (Decimal, AmountOfSubstanceUnit) -> Value
) = byMultiplying(molality, weight, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.amountOfSubstance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("amountOfSubstanceFromMolarityAndVolumeDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    MolarityUnit : Molarity,
    VolumeUnit : Volume
    > AmountOfSubstanceUnit.amountOfSubstance(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>,
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = amountOfSubstance(molarity, volume, ::DefaultScientificValue)

@JvmName("amountOfSubstanceFromMolarityAndVolume")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    MolarityUnit : Molarity,
    VolumeUnit : Volume,
    Value : ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
    > AmountOfSubstanceUnit.amountOfSubstance(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>,
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    factory: (Decimal, AmountOfSubstanceUnit) -> Value
) = byMultiplying(molarity, volume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.amountOfSubstance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("amountOfSubstanceFromVolumeAndMolarVolumeDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    VolumeUnit : Volume,
    MolarVolumeUnit : MolarVolume
    > AmountOfSubstanceUnit.amountOfSubstance(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>,
) = amountOfSubstance(volume, molarVolume, ::DefaultScientificValue)

@JvmName("amountOfSubstanceFromVolumeAndMolarVolume")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    VolumeUnit : Volume,
    MolarVolumeUnit : MolarVolume,
    Value : ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
    > AmountOfSubstanceUnit.amountOfSubstance(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>,
    factory: (Decimal, AmountOfSubstanceUnit) -> Value
) = byDividing(volume, molarVolume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.amountOfSubstance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("amountOfSubstanceFromWeightAndMolarMassDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    WeightUnit : Weight,
    MolarMassUnit : MolarMass
    > AmountOfSubstanceUnit.amountOfSubstance(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = amountOfSubstance(weight, molarMass, ::DefaultScientificValue)

@JvmName("amountOfSubstanceFromWeightAndMolarMass")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    WeightUnit : Weight,
    MolarMassUnit : MolarMass,
    Value : ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
    > AmountOfSubstanceUnit.amountOfSubstance(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    factory: (Decimal, AmountOfSubstanceUnit) -> Value
) = byDividing(weight, molarMass, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.amountOfSubstance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.catalysticActivity.catalysticActivity
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.Katal
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("amountOfSubstanceDivTime")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance, TimeUnit : Time> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Katal.catalysticActivity(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.amountOfSubstance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarEnergy.times
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.ImperialMolarEnergy
import com.splendo.kaluga.scientific.unit.MetricAndImperialMolarEnergy
import com.splendo.kaluga.scientific.unit.MetricMolarEnergy
import com.splendo.kaluga.scientific.unit.MolarEnergy
import kotlin.jvm.JvmName

@JvmName("amountOfSubstanceTimesMetricAndImperialMolarEnergy")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.times(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>
) = molarEnergy * this

@JvmName("amountOfSubstanceTimesMetricMolarEnergy")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.times(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MetricMolarEnergy>
) = molarEnergy * this

@JvmName("amountOfSubstanceTimesImperialMolarEnergy")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.times(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, ImperialMolarEnergy>
) = molarEnergy * this

@JvmName("amountOfSubstanceTimesMolarEnergy")
infix operator fun <MolarEnergyUnit : MolarEnergy, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.times(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>
) = molarEnergy * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.amountOfSubstance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molality.molality
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("amountOfSubstanceDivMetricWeight")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance, WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).molality(this, weight)

@JvmName("amountOfSubstanceDivImperialWeight")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance, WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).molality(this, weight)

@JvmName("amountOfSubstanceDivUKImperialWeight")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance, WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).molality(this, weight)

@JvmName("amountOfSubstanceDivUSCustomaryWeight")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance, WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).molality(this, weight)

@JvmName("amountOfSubstanceDivWeight")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance, WeightUnit : Weight> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per Kilogram).molality(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.amountOfSubstance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarity.molarity
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialVolume
import com.splendo.kaluga.scientific.unit.MetricVolume
import com.splendo.kaluga.scientific.unit.UKImperialVolume
import com.splendo.kaluga.scientific.unit.USCustomaryVolume
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("amountOfSubstanceDivMetricVolume")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance, VolumeUnit : MetricVolume> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = (unit per volume.unit).molarity(this, volume)

@JvmName("amountOfSubstanceDivImperialVolume")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance, VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = (unit per volume.unit).molarity(this, volume)

@JvmName("amountOfSubstanceDivUKImperialVolume")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance, VolumeUnit : UKImperialVolume> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = (unit per volume.unit).molarity(this, volume)

@JvmName("amountOfSubstanceDivUSCustomaryVolume")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance, VolumeUnit : USCustomaryVolume> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = (unit per volume.unit).molarity(this, volume)

@JvmName("amountOfSubstanceDivVolume")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance, VolumeUnit : Volume> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = (unit per CubicMeter).molarity(this, volume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.amountOfSubstance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.radioactivity.radioactivity
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.Becquerel
import com.splendo.kaluga.scientific.unit.Time

infix fun <AmountOfSubstanceUnit : AmountOfSubstance, TimeUnit : Time> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.decaysWithHalfLife(
    halfLife: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Becquerel.radioactivity(this, halfLife)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.amountOfSubstance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.CatalysticActivity
import com.splendo.kaluga.scientific.unit.Second
import kotlin.jvm.JvmName

@JvmName("amountOfSubstanceDivCatalysticActivity")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance, CatalysisUnit : CatalysticActivity> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    catalysis: ScientificValue<PhysicalQuantity.CatalysticActivity, CatalysisUnit>
) = Second.time(this, catalysis)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.amountOfSubstance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarVolume.times
import com.splendo.kaluga.scientific.converter.volume.volume
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialMolarVolume
import com.splendo.kaluga.scientific.unit.ImperialMolarity
import com.splendo.kaluga.scientific.unit.MetricMolarVolume
import com.splendo.kaluga.scientific.unit.MetricMolarity
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.UKImperialMolarVolume
import com.splendo.kaluga.scientific.unit.UKImperialMolarity
import com.splendo.kaluga.scientific.unit.USCustomaryMolarVolume
import com.splendo.kaluga.scientific.unit.USCustomaryMolarity
import kotlin.jvm.JvmName

@JvmName("amountOfSubstanceDivMetricMolarity")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MetricMolarity>
) = molarity.unit.per.volume(this, molarity)

@JvmName("amountOfSubstanceDivImperialMolarity")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    molarity: ScientificValue<PhysicalQuantity.Molarity, ImperialMolarity>
) = molarity.unit.per.volume(this, molarity)

@JvmName("amountOfSubstanceDivUKImperialMolarity")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    molarity: ScientificValue<PhysicalQuantity.Molarity, UKImperialMolarity>
) = molarity.unit.per.volume(this, molarity)

@JvmName("amountOfSubstanceDivUSCustomaryMolarity")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    molarity: ScientificValue<PhysicalQuantity.Molarity, USCustomaryMolarity>
) = molarity.unit.per.volume(this, molarity)

@JvmName("amountOfSubstanceDivMolarity")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance, MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = CubicMeter.volume(this, molarity)

@JvmName("amountOfSubstanceTimesMetricMolarVolume")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.times(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MetricMolarVolume>
) = molarVolume * this

@JvmName("amountOfSubstanceTimesImperialMolarVolume")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.times(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, ImperialMolarVolume>
) = molarVolume * this

@JvmName("amountOfSubstanceTimesUKImperialMolarVolume")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.times(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, UKImperialMolarVolume>
) = molarVolume * this

@JvmName("amountOfSubstanceTimesUSCustomaryMolarVolume")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.times(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, USCustomaryMolarVolume>
) = molarVolume * this

@JvmName("amountOfSubstanceTimesMolarVolume")
infix operator fun <MolarVolumeUnit : MolarVolume, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.times(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
) = molarVolume * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.amountOfSubstance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarMass.times
import com.splendo.kaluga.scientific.converter.weight.weight
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.ImperialMolality
import com.splendo.kaluga.scientific.unit.ImperialMolarMass
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricMolality
import com.splendo.kaluga.scientific.unit.MetricMolarMass
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.UKImperialMolality
import com.splendo.kaluga.scientific.unit.UKImperialMolarMass
import com.splendo.kaluga.scientific.unit.USCustomaryMolality
import com.splendo.kaluga.scientific.unit.USCustomaryMolarMass
import kotlin.jvm.JvmName

@JvmName("amountOfSubstanceDivMetricMolality")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, MetricMolality>
) = molality.unit.per.weight(this, molality)

@JvmName("amountOfSubstanceDivImperialMolality")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, ImperialMolality>
) = molality.unit.per.weight(this, molality)

@JvmName("amountOfSubstanceDivUKImperialMolality")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, UKImperialMolality>
) = molality.unit.per.weight(this, molality)

@JvmName("amountOfSubstanceDivUSCustomaryMolality")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, USCustomaryMolality>
) = molality.unit.per.weight(this, molality)

@JvmName("amountOfSubstanceDivMolality")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance, MolalityUnit : Molality> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = Kilogram.weight(this, molality)

@JvmName("amountOfSubstanceTimesMetricMolarMass")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MetricMolarMass>
) = molarMass * this

@JvmName("amountOfSubstanceTimesImperialMolarMass")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>
) = molarMass * this

@JvmName("amountOfSubstanceTimesUKImperialMolarMass")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, UKImperialMolarMass>
) = molarMass * this

@JvmName("amountOfSubstanceTimesUSCustomaryMolarMass")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, USCustomaryMolarMass>
) = molarMass * this

@JvmName("amountOfSubstanceTimesMolarMass")
infix operator fun <MolarMassUnit : MolarMass, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = molarMass * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.angle

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Angle
import com.splendo.kaluga.scientific.unit.AngularVelocity
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("angleFromAngularVelocityAndTimeDefault")
fun <
    AngleUnit : Angle,
    TimeUnit : Time,
    > AngleUnit.angle(
    velocity: ScientificValue<PhysicalQuantity.AngularVelocity, AngularVelocity>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = angle(velocity, time, ::DefaultScientificValue)

@JvmName("angleFromAngularVelocityAndTime")
fun <
    AngleUnit : Angle,
    TimeUnit : Time,
    Value : ScientificValue<PhysicalQuantity.Angle, AngleUnit>
    > AngleUnit.angle(
    velocity: ScientificValue<PhysicalQuantity.AngularVelocity, AngularVelocity>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, AngleUnit) -> Value
) = byMultiplying(velocity, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.angle

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.angularVelocity.velocity
import com.splendo.kaluga.scientific.unit.Angle
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("angleDivTime")
infix operator fun <AngleUnit : Angle, TimeUnit : Time> ScientificValue<PhysicalQuantity.Angle, AngleUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).velocity(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.angle

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.Angle
import com.splendo.kaluga.scientific.unit.AngularVelocity
import kotlin.jvm.JvmName

@JvmName("angleDivAngularVelocity")
infix operator fun <AngleUnit : Angle> ScientificValue<PhysicalQuantity.Angle, AngleUnit>.div(
    angularVelocity: ScientificValue<PhysicalQuantity.AngularVelocity, AngularVelocity>
) = angularVelocity.unit.per.time(this, angularVelocity)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.angularAcceleration

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AngularAcceleration
import com.splendo.kaluga.scientific.unit.AngularVelocity
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("angularAccelerationFromAngularVelocityAndTimeDefault")
fun <
    TimeUnit : Time
    > AngularAcceleration.acceleration(
    velocity: ScientificValue<PhysicalQuantity.AngularVelocity, AngularVelocity>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = acceleration(velocity, time, ::DefaultScientificValue)

@JvmName("angularAccelerationFromAngularVelocityAndTime")
fun <
    TimeUnit : Time,
    Value : ScientificValue<PhysicalQuantity.AngularAcceleration, AngularAcceleration>
    > AngularAcceleration.acceleration(
    velocity: ScientificValue<PhysicalQuantity.AngularVelocity, AngularVelocity>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, AngularAcceleration) -> Value
) = byDividing(velocity, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.angularAcceleration

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.angularVelocity.velocity
import com.splendo.kaluga.scientific.unit.AngularAcceleration
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("angularAccelerationTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.AngularAcceleration, AngularAcceleration>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.angularVelocity.velocity(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.angularVelocity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Angle
import com.splendo.kaluga.scientific.unit.AngularVelocity
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("angularVelocityFromAngleAndTimeDefault")
fun <
    AngleUnit : Angle,
    TimeUnit : Time
    > AngularVelocity.velocity(
    angle: ScientificValue<PhysicalQuantity.Angle, AngleUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = velocity(angle, time, ::DefaultScientificValue)

@JvmName("angularVelocityFromAngleAndTime")
fun <
    AngleUnit : Angle,
    TimeUnit : Time,
    Value : ScientificValue<PhysicalQuantity.AngularVelocity, AngularVelocity>
    > AngularVelocity.velocity(
    angle: ScientificValue<PhysicalQuantity.Angle, AngleUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, AngularVelocity) -> Value
) = byDividing(angle, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.angularVelocity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.AngularAcceleration
import com.splendo.kaluga.scientific.unit.AngularVelocity
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("angularVelocityFromAngularAccelerationAndTimeDefault")
fun <
    TimeUnit : Time,
    > AngularVelocity.velocity(
    acceleration: ScientificValue<PhysicalQuantity.AngularAcceleration, AngularAcceleration>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = velocity(acceleration, time, ::DefaultScientificValue)

@JvmName("angularVelocityFromAngularAccelerationAndTime")
fun <
    TimeUnit : Time,
    Value : ScientificValue<PhysicalQuantity.AngularVelocity, AngularVelocity>
    > AngularVelocity.velocity(
    acceleration: ScientificValue<PhysicalQuantity.AngularAcceleration, AngularAcceleration>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, AngularVelocity) -> Value
) = byMultiplying(acceleration, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.angularVelocity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.angle.angle
import com.splendo.kaluga.scientific.unit.AngularVelocity
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("angularVelocityTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.AngularVelocity, AngularVelocity>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.angle.angle(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.angularVelocity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.angularAcceleration.acceleration
import com.splendo.kaluga.scientific.unit.AngularVelocity
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("angularVelocityDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.AngularVelocity, AngularVelocity>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).acceleration(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.angularVelocity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.AngularAcceleration
import com.splendo.kaluga.scientific.unit.AngularVelocity
import kotlin.jvm.JvmName

@JvmName("angularVelocityDivAngularAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.AngularVelocity, AngularVelocity>.div(
    angularAcceleration: ScientificValue<PhysicalQuantity.AngularAcceleration, AngularAcceleration>
) = angularAcceleration.unit.per.time(this, angularAcceleration)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.SurfaceTension
import kotlin.jvm.JvmName

@JvmName("areaFromEnergyAndSurfaceTensionDefault")
fun <
    EnergyUnit : Energy,
    AreaUnit : Area,
    SurfaceTensionUnit : SurfaceTension
    > AreaUnit.area(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>
) = area(energy, surfaceTension, ::DefaultScientificValue)

@JvmName("areaFromEnergyAndSurfaceTension")
fun <
    EnergyUnit : Energy,
    AreaUnit : Area,
    SurfaceTensionUnit : SurfaceTension,
    Value : ScientificValue<PhysicalQuantity.Area, AreaUnit>
    > AreaUnit.area(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>,
    factory: (Decimal, AreaUnit) -> Value
) = byDividing(energy, surfaceTension, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Pressure
import kotlin.jvm.JvmName

@JvmName("areaFromForceAndPressureDefault")
fun <
    ForceUnit : Force,
    AreaUnit : Area,
    PressureUnit : Pressure
    > AreaUnit.area(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = area(force, pressure, ::DefaultScientificValue)

@JvmName("areaFromForceAndPressure")
fun <
    ForceUnit : Force,
    AreaUnit : Area,
    PressureUnit : Pressure,
    Value : ScientificValue<PhysicalQuantity.Area, AreaUnit>
    > AreaUnit.area(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>,
    factory: (Decimal, AreaUnit) -> Value
) = byDividing(force, pressure, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Length
import kotlin.jvm.JvmName

@JvmName("areaFromLengthAndWidthDefault")
fun <
    LengthUnit : Length,
    WidthUnit : Length,
    AreaUnit : Area
    > AreaUnit.area(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    width: ScientificValue<PhysicalQuantity.Length, WidthUnit>
) = area(length, width, ::DefaultScientificValue)

@JvmName("areaFromLengthAndWidth")
fun <
    LengthUnit : Length,
    WidthUnit : Length,
    AreaUnit : Area,
    Value : ScientificValue<PhysicalQuantity.Area, AreaUnit>
    > AreaUnit.area(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    width: ScientificValue<PhysicalQuantity.Length, WidthUnit>,
    factory: (Decimal, AreaUnit) -> Value
) = byMultiplying(length, width, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import kotlin.jvm.JvmName

@JvmName("areaFromLinearMassDensityAndDensityDefault")
fun <
    DensityUnit : Density,
    AreaUnit : Area,
    LinearMassDensityUnit : LinearMassDensity
    > AreaUnit.area(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>,
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = area(linearMassDensity, density, ::DefaultScientificValue)

@JvmName("areaFromLinearMassDensityAndDensity")
fun <
    DensityUnit : Density,
    AreaUnit : Area,
    LinearMassDensityUnit : LinearMassDensity,
    Value : ScientificValue<PhysicalQuantity.Area, AreaUnit>
    > AreaUnit.area(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>,
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    factory: (Decimal, AreaUnit) -> Value
) = byDividing(linearMassDensity, density, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("areaFromLinearMassDensityAndSpecificVolumeDefault")
fun <
    SpecificVolumeUnit : SpecificVolume,
    AreaUnit : Area,
    LinearMassDensityUnit : LinearMassDensity
    > AreaUnit.area(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>
) = area(specificVolume, linearMassDensity, ::DefaultScientificValue)

@JvmName("areaFromLinearMassDensityAndSpecificVolume")
fun <
    SpecificVolumeUnit : SpecificVolume,
    AreaUnit : Area,
    LinearMassDensityUnit : LinearMassDensity,
    Value : ScientificValue<PhysicalQuantity.Area, AreaUnit>
    > AreaUnit.area(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>,
    factory: (Decimal, AreaUnit) -> Value
) = byMultiplying(specificVolume, linearMassDensity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.LuminousEnergy
import com.splendo.kaluga.scientific.unit.LuminousExposure
import kotlin.jvm.JvmName

@JvmName("areaFromLuminousEnergyAndExposureDefault")
fun <
    LuminousExposureUnit : LuminousExposure,
    AreaUnit : Area
    > AreaUnit.area(
    luminousEnergy: ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>,
    luminousExposure: ScientificValue<PhysicalQuantity.LuminousExposure, LuminousExposureUnit>
) = area(luminousEnergy, luminousExposure, ::DefaultScientificValue)

@JvmName("areaFromLuminousEnergyAndExposure")
fun <
    LuminousExposureUnit : LuminousExposure,
    AreaUnit : Area,
    Value : ScientificValue<PhysicalQuantity.Area, AreaUnit>
    > AreaUnit.area(
    luminousEnergy: ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>,
    luminousExposure: ScientificValue<PhysicalQuantity.LuminousExposure, LuminousExposureUnit>,
    factory: (Decimal, AreaUnit) -> Value
) = byDividing(luminousEnergy, luminousExposure, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.LuminousFlux
import kotlin.jvm.JvmName

@JvmName("areaFromFluxAndIlluminanceDefault")
fun <
    FluxUnit : LuminousFlux,
    AreaUnit : Area,
    IlluminanceUnit : Illuminance
    > AreaUnit.area(
    flux: ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>,
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>
) = area(flux, illuminance, ::DefaultScientificValue)

@JvmName("areaFromFluxAndIlluminance")
fun <
    FluxUnit : LuminousFlux,
    AreaUnit : Area,
    IlluminanceUnit : Illuminance,
    Value : ScientificValue<PhysicalQuantity.Area, AreaUnit>
    > AreaUnit.area(
    flux: ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>,
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>,
    factory: (Decimal, AreaUnit) -> Value
) = byDividing(flux, illuminance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Luminance
import com.splendo.kaluga.scientific.unit.LuminousIntensity
import kotlin.jvm.JvmName

@JvmName("areaFromLuminousIntensityAndLuminanceDefault")
fun <
    LuminousIntensityUnit : LuminousIntensity,
    AreaUnit : Area,
    LuminanceUnit : Luminance
    > AreaUnit.area(
    luminousIntensity: ScientificValue<PhysicalQuantity.LuminousIntensity, LuminousIntensityUnit>,
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>
) = area(luminousIntensity, luminance, ::DefaultScientificValue)

@JvmName("areaFromLuminousIntensityAndLuminance")
fun <
    LuminousIntensityUnit : LuminousIntensity,
    AreaUnit : Area,
    LuminanceUnit : Luminance,
    Value : ScientificValue<PhysicalQuantity.Area, AreaUnit>
    > AreaUnit.area(
    luminousIntensity: ScientificValue<PhysicalQuantity.LuminousIntensity, LuminousIntensityUnit>,
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>,
    factory: (Decimal, AreaUnit) -> Value
) = byDividing(luminousIntensity, luminance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.MagneticInduction
import kotlin.jvm.JvmName

@JvmName("areaFromFluxAndInductionDefault")
fun <
    FluxUnit : MagneticFlux,
    AreaUnit : Area,
    InductionUnit : MagneticInduction
    > AreaUnit.area(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    induction: ScientificValue<PhysicalQuantity.MagneticInduction, InductionUnit>
) = area(flux, induction, ::DefaultScientificValue)

@JvmName("areaFromFluxAndInduction")
fun <
    FluxUnit : MagneticFlux,
    AreaUnit : Area,
    InductionUnit : MagneticInduction,
    Value : ScientificValue<PhysicalQuantity.Area, AreaUnit>
    > AreaUnit.area(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    induction: ScientificValue<PhysicalQuantity.MagneticInduction, InductionUnit>,
    factory: (Decimal, AreaUnit) -> Value
) = byDividing(flux, induction, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.DynamicViscosity
import com.splendo.kaluga.scientific.unit.Momentum
import kotlin.jvm.JvmName

@JvmName("areaFromMomentumAndDynamicViscosityDefault")
fun <
    MomentumUnit : Momentum,
    AreaUnit : Area,
    DynamicViscosityUnit : DynamicViscosity
    > AreaUnit.area(
    momentum: ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>,
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, DynamicViscosityUnit>
) = area(momentum, dynamicViscosity, ::DefaultScientificValue)

@JvmName("areaFromMomentumAndDynamicViscosity")
fun <
    MomentumUnit : Momentum,
    AreaUnit : Area,
    DynamicViscosityUnit : DynamicViscosity,
    Value : ScientificValue<PhysicalQuantity.Area, AreaUnit>
    > AreaUnit.area(
    momentum: ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>,
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, DynamicViscosityUnit>,
    factory: (Decimal, AreaUnit) -> Value
) = byDividing(momentum, dynamicViscosity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("areaFromVolumeAndHeightDefault")
fun <
    HeightUnit : Length,
    AreaUnit : Area,
    VolumeUnit : Volume
    > AreaUnit.area(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    height: ScientificValue<PhysicalQuantity.Length, HeightUnit>
) = area(volume, height, ::DefaultScientificValue)

@JvmName("areaFromVolumeAndHeight")
fun <
    HeightUnit : Length,
    AreaUnit : Area,
    VolumeUnit : Volume,
    Value : ScientificValue<PhysicalQuantity.Area, AreaUnit>
    > AreaUnit.area(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    height: ScientificValue<PhysicalQuantity.Length, HeightUnit>,
    factory: (Decimal, AreaUnit) -> Value
) = byDividing(volume, height, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.VolumetricFlow
import com.splendo.kaluga.scientific.unit.VolumetricFlux
import kotlin.jvm.JvmName

@JvmName("areaFromVolumetricFlowAndVolumetricFluxDefault")
fun <
    VolumetricFlowUnit : VolumetricFlow,
    AreaUnit : Area,
    VolumetricFluxUnit : VolumetricFlux
    > AreaUnit.area(
    volumetricFlow: ScientificValue<PhysicalQuantity.VolumetricFlow, VolumetricFlowUnit>,
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, VolumetricFluxUnit>
) = area(volumetricFlow, volumetricFlux, ::DefaultScientificValue)

@JvmName("areaFromVolumetricFlowAndVolumetricFlux")
fun <
    VolumetricFlowUnit : VolumetricFlow,
    AreaUnit : Area,
    VolumetricFluxUnit : VolumetricFlux,
    Value : ScientificValue<PhysicalQuantity.Area, AreaUnit>
    > AreaUnit.area(
    volumetricFlow: ScientificValue<PhysicalQuantity.VolumetricFlow, VolumetricFlowUnit>,
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, VolumetricFluxUnit>,
    factory: (Decimal, AreaUnit) -> Value
) = byDividing(volumetricFlow, volumetricFlux, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("areaFromWeightAndAreaDensityDefault")
fun <
    WeightUnit : Weight,
    AreaUnit : Area,
    AreaDensityUnit : AreaDensity
    > AreaUnit.area(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>
) = area(weight, areaDensity, ::DefaultScientificValue)

@JvmName("areaFromWeightAndAreaDensity")
fun <
    WeightUnit : Weight,
    AreaUnit : Area,
    AreaDensityUnit : AreaDensity,
    Value : ScientificValue<PhysicalQuantity.Area, AreaUnit>
    > AreaUnit.area(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>,
    factory: (Decimal, AreaUnit) -> Value
) = byDividing(weight, areaDensity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.surfaceTension.times
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialSurfaceTension
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricSurfaceTension
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.SquareInch
import com.splendo.kaluga.scientific.unit.SurfaceTension
import com.splendo.kaluga.scientific.unit.UKImperialSurfaceTension
import com.splendo.kaluga.scientific.unit.USCustomarySurfaceTension
import kotlin.jvm.JvmName

@JvmName("squareCentimeterTimesMetricSurfaceTension")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareCentimeter>.times(surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, MetricSurfaceTension>) =
    surfaceTension * this

@JvmName("metricAreaTimesMetricSurfaceTension")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, MetricSurfaceTension>
) = surfaceTension * this

@JvmName("squareInchTimesImperialSurfaceTension")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareInch>.times(surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, ImperialSurfaceTension>) =
    surfaceTension * this

@JvmName("squareInchTimesUKImperialSurfaceTension")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareInch>.times(surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, UKImperialSurfaceTension>) =
    surfaceTension * this

@JvmName("squareInchTimesUSCustomarySurfaceTension")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareInch>.times(surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, USCustomarySurfaceTension>) =
    surfaceTension * this

@JvmName("imperialAreaTimesImperialSurfaceTensionTimes")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, ImperialSurfaceTension>
) = surfaceTension * this

@JvmName("imperialAreaTimesUKImperialSurfaceTension")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, UKImperialSurfaceTension>
) = surfaceTension * this

@JvmName("imperialAreaTimesUSCustomarySurfaceTension")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, USCustomarySurfaceTension>
) = surfaceTension * this

@JvmName("areaTimesSurfaceTension")
infix operator fun <SurfaceTensionUnit : SurfaceTension, AreaUnit : Area> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>
) = surfaceTension * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.pressure.times
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Barye
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialPressure
import com.splendo.kaluga.scientific.unit.ImperialTonSquareFoot
import com.splendo.kaluga.scientific.unit.ImperialTonSquareInch
import com.splendo.kaluga.scientific.unit.KipSquareFoot
import com.splendo.kaluga.scientific.unit.KipSquareInch
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.OunceSquareInch
import com.splendo.kaluga.scientific.unit.Pressure
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.UKImperialPressure
import com.splendo.kaluga.scientific.unit.USCustomaryPressure
import com.splendo.kaluga.scientific.unit.USTonSquareFoot
import com.splendo.kaluga.scientific.unit.USTonSquareInch
import kotlin.jvm.JvmName

@JvmName("squareCentimeterTimesBarye")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareCentimeter>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, Barye>) =
    pressure * this

@JvmName("squareCentimeterTimesBaryeMultiple")
infix operator fun <BaryeUnit> ScientificValue<PhysicalQuantity.Area, SquareCentimeter>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, BaryeUnit>
) where BaryeUnit : Pressure, BaryeUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> =
    pressure * this

@JvmName("imperialAreaTimesOunceSquareInch")
infix operator fun <Area : ImperialArea> ScientificValue<PhysicalQuantity.Area, Area>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, OunceSquareInch>) =
    pressure * this

@JvmName("imperialAreaTimesKipSquareInch")
infix operator fun <Area : ImperialArea> ScientificValue<PhysicalQuantity.Area, Area>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, KipSquareInch>) =
    pressure * this

@JvmName("imperialAreaTimesKipSquareFeet")
infix operator fun <Area : ImperialArea> ScientificValue<PhysicalQuantity.Area, Area>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, KipSquareFoot>) =
    pressure * this

@JvmName("imperialAreaTimesUsTonSquareInch")
infix operator fun <Area : ImperialArea> ScientificValue<PhysicalQuantity.Area, Area>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, USTonSquareInch>) =
    pressure * this

@JvmName("imperialAreaTimesUsTonSquareFeet")
infix operator fun <Area : ImperialArea> ScientificValue<PhysicalQuantity.Area, Area>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, USTonSquareFoot>) =
    pressure * this

@JvmName("imperialAreaTimesImperialTonSquareInch")
infix operator fun <Area : ImperialArea> ScientificValue<PhysicalQuantity.Area, Area>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, ImperialTonSquareInch>) =
    pressure * this

@JvmName("imperialAreaTimesImperialTonSquareFeet")
infix operator fun <Area : ImperialArea> ScientificValue<PhysicalQuantity.Area, Area>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, ImperialTonSquareFoot>) =
    pressure * this

@JvmName("imperialAreaTimesImperialPressure")
infix operator fun <Pressure : ImperialPressure, Area : ImperialArea> ScientificValue<PhysicalQuantity.Area, Area>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, Pressure>
) = pressure * this

@JvmName("imperialAreaTimesUKImperialPressure")
infix operator fun <Pressure : UKImperialPressure, Area : ImperialArea> ScientificValue<PhysicalQuantity.Area, Area>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, Pressure>
) = pressure * this

@JvmName("imperialAreaTimesUSCustomaryPressure")
infix operator fun <Pressure : USCustomaryPressure, Area : ImperialArea> ScientificValue<PhysicalQuantity.Area, Area>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, Pressure>
) = pressure * this

@JvmName("areaTimesPressure")
infix operator fun <PressureUnit : Pressure, AreaUnit : Area> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = pressure * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.length.width
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Centimeter
import com.splendo.kaluga.scientific.unit.Decameter
import com.splendo.kaluga.scientific.unit.Decimeter
import com.splendo.kaluga.scientific.unit.Foot
import com.splendo.kaluga.scientific.unit.Gigameter
import com.splendo.kaluga.scientific.unit.Hectometer
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.Inch
import com.splendo.kaluga.scientific.unit.Kilometer
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.Megameter
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.Micrometer
import com.splendo.kaluga.scientific.unit.Mile
import com.splendo.kaluga.scientific.unit.Millimeter
import com.splendo.kaluga.scientific.unit.Nanometer
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.SquareDecameter
import com.splendo.kaluga.scientific.unit.SquareDecimeter
import com.splendo.kaluga.scientific.unit.SquareFoot
import com.splendo.kaluga.scientific.unit.SquareGigameter
import com.splendo.kaluga.scientific.unit.SquareHectometer
import com.splendo.kaluga.scientific.unit.SquareInch
import com.splendo.kaluga.scientific.unit.SquareKilometer
import com.splendo.kaluga.scientific.unit.SquareMegameter
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.SquareMicrometer
import com.splendo.kaluga.scientific.unit.SquareMile
import com.splendo.kaluga.scientific.unit.SquareMillimeter
import com.splendo.kaluga.scientific.unit.SquareNanometer
import com.splendo.kaluga.scientific.unit.SquareYard
import com.splendo.kaluga.scientific.unit.Yard
import kotlin.jvm.JvmName

@JvmName("squareMeterDivMeter")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareMeter>.div(length: ScientificValue<PhysicalQuantity.Length, Meter>) =
    Meter.width(this, length)

@JvmName("squareNanometerDivNanometer")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareNanometer>.div(length: ScientificValue<PhysicalQuantity.Length, Nanometer>) =
    Nanometer.width(this, length)

@JvmName("squareMicrometerDivMicrometer")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareMicrometer>.div(length: ScientificValue<PhysicalQuantity.Length, Micrometer>) =
    Micrometer.width(this, length)

@JvmName("squareMillimeterDivMillimeter")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareMillimeter>.div(length: ScientificValue<PhysicalQuantity.Length, Millimeter>) =
    Millimeter.width(this, length)

@JvmName("squareCentimeterDivCentimeter")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareCentimeter>.div(length: ScientificValue<PhysicalQuantity.Length, Centimeter>) =
    Centimeter.width(this, length)

@JvmName("squareDecimeterDivDecimeter")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareDecimeter>.div(length: ScientificValue<PhysicalQuantity.Length, Decimeter>) =
    Decimeter.width(this, length)

@JvmName("squareDecameterDivDecameter")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareDecameter>.div(length: ScientificValue<PhysicalQuantity.Length, Decameter>) =
    Decameter.width(this, length)

@JvmName("squareHectometerDivHectometer")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareHectometer>.div(length: ScientificValue<PhysicalQuantity.Length, Hectometer>) =
    Hectometer.width(this, length)

@JvmName("squareKilometerDivKilometer")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareKilometer>.div(length: ScientificValue<PhysicalQuantity.Length, Kilometer>) =
    Kilometer.width(this, length)

@JvmName("squareMegameterDivMegameter")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareMegameter>.div(length: ScientificValue<PhysicalQuantity.Length, Megameter>) =
    Megameter.width(this, length)

@JvmName("squareGigameterDivGigameter")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareGigameter>.div(length: ScientificValue<PhysicalQuantity.Length, Gigameter>) =
    Gigameter.width(this, length)

@JvmName("metricAreaDivMetricLength")
infix operator fun <AreaUnit : MetricArea, LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.Area, AreaUnit>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = Meter.width(this, length)

@JvmName("squareInchDivInch")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareInch>.div(length: ScientificValue<PhysicalQuantity.Length, Inch>) =
    Inch.width(this, length)

@JvmName("squareFootDivFoot")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareFoot>.div(length: ScientificValue<PhysicalQuantity.Length, Foot>) =
    Foot.width(this, length)

@JvmName("squareYardDivYard")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareYard>.div(length: ScientificValue<PhysicalQuantity.Length, Yard>) =
    Yard.width(this, length)

@JvmName("squareMileDivMile")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareMile>.div(length: ScientificValue<PhysicalQuantity.Length, Mile>) =
    Mile.width(this, length)

@JvmName("imperialAreaDivImperialLength")
infix operator fun <AreaUnit : ImperialArea, LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Area, AreaUnit>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = Foot.width(this, length)

@JvmName("areaDivLength")
infix operator fun <AreaUnit : Area, LengthUnit : Length> ScientificValue<PhysicalQuantity.Area, AreaUnit>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = Meter.width(this, length)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.density.times
import com.splendo.kaluga.scientific.converter.linearMassDensity.linearMassDensity
import com.splendo.kaluga.scientific.converter.volume.div
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricAreaTimesMetricDensity")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, MetricDensity>
) = density * this

@JvmName("imperialAreaTimesImperialDensity")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>
) = density * this

@JvmName("imperialAreaTimesUKImperialDensity")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, UKImperialDensity>
) = density * this

@JvmName("imperialAreaTimesUSCustomaryDensity")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>
) = density * this

@JvmName("areaTimesDensity")
infix operator fun <DensityUnit : Density, AreaUnit : Area> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = density * this

@JvmName("metricAreaDivMetricSpecificVolume")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>
) = (specificVolume.unit.per per (1(specificVolume.unit.volume) / 1(unit)).unit).linearMassDensity(
    this,
    specificVolume
)

@JvmName("imperialAreaDivImperialSpecificVolume")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = (specificVolume.unit.per per (1(specificVolume.unit.volume) / 1(unit)).unit).linearMassDensity(
    this,
    specificVolume
)

@JvmName("imperialAreaDivUKImperialSpecificVolume")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>
) = (specificVolume.unit.per per (1(specificVolume.unit.volume) / 1(unit)).unit).linearMassDensity(
    this,
    specificVolume
)

@JvmName("imperialAreaDivUSCustomarySpecificVolume")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>
) = (specificVolume.unit.per per (1(specificVolume.unit.volume) / 1(unit)).unit).linearMassDensity(
    this,
    specificVolume
)

@JvmName("areaDivSpecificVolume")
infix operator fun <SpecificVolumeUnit : SpecificVolume, AreaUnit : Area> ScientificValue<PhysicalQuantity.Area, AreaUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = (Kilogram per Meter).linearMassDensity(this, specificVolume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.luminousExposure.times
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialLuminousExposure
import com.splendo.kaluga.scientific.unit.LuminousExposure
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricLuminousExposure
import kotlin.jvm.JvmName

@JvmName("metricAreaTimesMetricExposure")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    luminousExposure: ScientificValue<PhysicalQuantity.LuminousExposure, MetricLuminousExposure>
) = luminousExposure * this

@JvmName("imperialAreaTimesImperialExposure")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    luminousExposure: ScientificValue<PhysicalQuantity.LuminousExposure, ImperialLuminousExposure>
) = luminousExposure * this

@JvmName("areaTimesExposure")
infix operator fun <ExposureUnit : LuminousExposure, AreaUnit : Area> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    luminousExposure: ScientificValue<PhysicalQuantity.LuminousExposure, ExposureUnit>
) = luminousExposure * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.illuminance.times
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Illuminance
import kotlin.jvm.JvmName

@JvmName("areaTimesIlluminance")
infix operator fun <IlluminanceUnit : Illuminance, AreaUnit : Area> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>
) = illuminance * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.luminance.times
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Luminance
import kotlin.jvm.JvmName

@JvmName("areaTimesLuminance")
infix operator fun <LuminanceUnit : Luminance, AreaUnit : Area> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>
) = luminance * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.magneticInduction.times
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Gauss
import com.splendo.kaluga.scientific.unit.MagneticInduction
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import kotlin.jvm.JvmName

@JvmName("squareCentimeterTimesGauss")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareCentimeter>.times(induction: ScientificValue<PhysicalQuantity.MagneticInduction, Gauss>) =
    induction * this

@JvmName("areaTimesInduction")
infix operator fun <InductionUnit : MagneticInduction, AreaUnit : Area> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    induction: ScientificValue<PhysicalQuantity.MagneticInduction, InductionUnit>
) = induction * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.dynamicViscosity.times
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.DynamicViscosity
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialDynamicViscosity
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricDynamicViscosity
import com.splendo.kaluga.scientific.unit.UKImperialDynamicViscosity
import com.splendo.kaluga.scientific.unit.USCustomaryDynamicViscosity
import kotlin.jvm.JvmName

@JvmName("metricAreaTimesMetricDynamicViscosity")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, MetricDynamicViscosity>
) = dynamicViscosity * this

@JvmName("imperialAreaTimesImperialDynamicViscosity")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, ImperialDynamicViscosity>
) = dynamicViscosity * this

@JvmName("imperialAreaTimesUKImperialDynamicViscosity")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, UKImperialDynamicViscosity>
) = dynamicViscosity * this

@JvmName("imperialAreaTimesUSCustomaryDynamicViscosity")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, USCustomaryDynamicViscosity>
) = dynamicViscosity * this

@JvmName("areaTimesDynamicViscosity")
infix operator fun <DynamicViscosityUnit : DynamicViscosity, AreaUnit : Area> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, DynamicViscosityUnit>
) = dynamicViscosity * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.length.times
import com.splendo.kaluga.scientific.converter.specificVolume.specificVolume
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricLinearMassDensity
import com.splendo.kaluga.scientific.unit.UKImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.USCustomaryLinearMassDensity
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricAreaDivMetricLinearMassDensity")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.div(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, MetricLinearMassDensity>
) =
    ((1(linearMassDensity.unit.per) * 1(unit)).unit per linearMassDensity.unit.weight).specificVolume(
        this,
        linearMassDensity
    )

@JvmName("imperialAreaDivImperialLinearMassDensity")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.div(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>
) =
    ((1(linearMassDensity.unit.per) * 1(unit)).unit per linearMassDensity.unit.weight).specificVolume(
        this,
        linearMassDensity
    )

@JvmName("imperialAreaDivUKImperialLinearMassDensity")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.div(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, UKImperialLinearMassDensity>
) =
    ((1(linearMassDensity.unit.per) * 1(unit)).unit per linearMassDensity.unit.weight).specificVolume(
        this,
        linearMassDensity
    )

@JvmName("imperialAreaDivUSCustomaryLinearMassDensity")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.div(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, USCustomaryLinearMassDensity>
) =
    ((1(linearMassDensity.unit.per) * 1(unit)).unit per linearMassDensity.unit.weight).specificVolume(
        this,
        linearMassDensity
    )

@JvmName("areaDivLinearMassDensity")
infix operator fun <LinearMassDensityUnit : LinearMassDensity, AreaUnit : Area> ScientificValue<PhysicalQuantity.Area, AreaUnit>.div(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>
) = (CubicMeter per Kilogram).specificVolume(this, linearMassDensity)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.volume.volume
import com.splendo.kaluga.scientific.unit.Acre
import com.splendo.kaluga.scientific.unit.AcreFoot
import com.splendo.kaluga.scientific.unit.AcreInch
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Centimeter
import com.splendo.kaluga.scientific.unit.CubicCentimeter
import com.splendo.kaluga.scientific.unit.CubicDecameter
import com.splendo.kaluga.scientific.unit.CubicDecimeter
import com.splendo.kaluga.scientific.unit.CubicFoot
import com.splendo.kaluga.scientific.unit.CubicGigameter
import com.splendo.kaluga.scientific.unit.CubicHectometer
import com.splendo.kaluga.scientific.unit.CubicInch
import com.splendo.kaluga.scientific.unit.CubicKilometer
import com.splendo.kaluga.scientific.unit.CubicMegameter
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.CubicMicrometer
import com.splendo.kaluga.scientific.unit.CubicMile
import com.splendo.kaluga.scientific.unit.CubicMillimeter
import com.splendo.kaluga.scientific.unit.CubicNanometer
import com.splendo.kaluga.scientific.unit.CubicYard
import com.splendo.kaluga.scientific.unit.Decameter
import com.splendo.kaluga.scientific.unit.Decimeter
import com.splendo.kaluga.scientific.unit.Foot
import com.splendo.kaluga.scientific.unit.Gigameter
import com.splendo.kaluga.scientific.unit.Hectometer
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.Inch
import com.splendo.kaluga.scientific.unit.Kilometer
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.Megameter
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.Micrometer
import com.splendo.kaluga.scientific.unit.Mile
import com.splendo.kaluga.scientific.unit.Millimeter
import com.splendo.kaluga.scientific.unit.Nanometer
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.SquareDecameter
import com.splendo.kaluga.scientific.unit.SquareDecimeter
import com.splendo.kaluga.scientific.unit.SquareFoot
import com.splendo.kaluga.scientific.unit.SquareGigameter
import com.splendo.kaluga.scientific.unit.SquareHectometer
import com.splendo.kaluga.scientific.unit.SquareInch
import com.splendo.kaluga.scientific.unit.SquareKilometer
import com.splendo.kaluga.scientific.unit.SquareMegameter
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.SquareMicrometer
import com.splendo.kaluga.scientific.unit.SquareMile
import com.splendo.kaluga.scientific.unit.SquareMillimeter
import com.splendo.kaluga.scientific.unit.SquareNanometer
import com.splendo.kaluga.scientific.unit.SquareYard
import com.splendo.kaluga.scientific.unit.Yard
import kotlin.jvm.JvmName

@JvmName("squareMeterTimesMeter")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareMeter>.times(height: ScientificValue<PhysicalQuantity.Length, Meter>) =
    CubicMeter.volume(this, height)

@JvmName("squareNanometerTimesNanometer")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareNanometer>.times(height: ScientificValue<PhysicalQuantity.Length, Nanometer>) =
    CubicNanometer.volume(this, height)

@JvmName("squareMicrometerTimesMicrometer")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareMicrometer>.times(height: ScientificValue<PhysicalQuantity.Length, Micrometer>) =
    CubicMicrometer.volume(this, height)

@JvmName("squareMillimeterTimesMillimeter")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareMillimeter>.times(height: ScientificValue<PhysicalQuantity.Length, Millimeter>) =
    CubicMillimeter.volume(this, height)

@JvmName("squareCentimeterTimesCentimeter")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareCentimeter>.times(height: ScientificValue<PhysicalQuantity.Length, Centimeter>) =
    CubicCentimeter.volume(this, height)

@JvmName("squareDecimeterTimesDecimeter")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareDecimeter>.times(height: ScientificValue<PhysicalQuantity.Length, Decimeter>) =
    CubicDecimeter.volume(this, height)

@JvmName("squareDecameterTimesDecameter")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareDecameter>.times(height: ScientificValue<PhysicalQuantity.Length, Decameter>) =
    CubicDecameter.volume(this, height)

@JvmName("squareHectometerTimesHectometer")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareHectometer>.times(height: ScientificValue<PhysicalQuantity.Length, Hectometer>) =
    CubicHectometer.volume(this, height)

@JvmName("squareKilometerTimesKilometer")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareKilometer>.times(height: ScientificValue<PhysicalQuantity.Length, Kilometer>) =
    CubicKilometer.volume(this, height)

@JvmName("squareMegameterTimesMegameter")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareMegameter>.times(height: ScientificValue<PhysicalQuantity.Length, Megameter>) =
    CubicMegameter.volume(this, height)

@JvmName("squareGigameterTimesGigameter")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareGigameter>.times(height: ScientificValue<PhysicalQuantity.Length, Gigameter>) =
    CubicGigameter.volume(this, height)

@JvmName("metricAreaTimesMetricLength")
infix operator fun <AreaUnit : MetricArea, HeightUnit : MetricLength> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    height: ScientificValue<PhysicalQuantity.Length, HeightUnit>
) = CubicMeter.volume(this, height)

@JvmName("squareInchTimesInch")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareInch>.times(height: ScientificValue<PhysicalQuantity.Length, Inch>) =
    CubicInch.volume(this, height)

@JvmName("squareFootTimesFoot")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareFoot>.times(height: ScientificValue<PhysicalQuantity.Length, Foot>) =
    CubicFoot.volume(this, height)

@JvmName("squareYardTimesYard")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareYard>.times(height: ScientificValue<PhysicalQuantity.Length, Yard>) =
    CubicYard.volume(this, height)

@JvmName("squareMileTimesMile")
infix operator fun ScientificValue<PhysicalQuantity.Area, SquareMile>.times(height: ScientificValue<PhysicalQuantity.Length, Mile>) =
    CubicMile.volume(this, height)

@JvmName("acreTimesInch")
infix operator fun ScientificValue<PhysicalQuantity.Area, Acre>.times(height: ScientificValue<PhysicalQuantity.Length, Inch>) =
    AcreInch.volume(this, height)

@JvmName("acreTimesFoot")
infix operator fun ScientificValue<PhysicalQuantity.Area, Acre>.times(height: ScientificValue<PhysicalQuantity.Length, Foot>) =
    AcreFoot.volume(this, height)

@JvmName("imperialAreaTimesImperialLength")
infix operator fun <AreaUnit : ImperialArea, HeightUnit : ImperialLength> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    height: ScientificValue<PhysicalQuantity.Length, HeightUnit>
) = CubicFoot.volume(this, height)

@JvmName("areaTimesLength")
infix operator fun <AreaUnit : Area, HeightUnit : Length> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    height: ScientificValue<PhysicalQuantity.Length, HeightUnit>
) = CubicMeter.volume(this, height)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.volumetricFlux.times
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialVolumetricFlux
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricVolumetricFlux
import com.splendo.kaluga.scientific.unit.UKImperialVolumetricFlux
import com.splendo.kaluga.scientific.unit.USCustomaryVolumetricFlux
import com.splendo.kaluga.scientific.unit.VolumetricFlux
import kotlin.jvm.JvmName

@JvmName("metricAreaTimesMetricVolumetricFlux")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, MetricVolumetricFlux>
) = volumetricFlux * this

@JvmName("imperialAreaTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, ImperialVolumetricFlux>
) = volumetricFlux * this

@JvmName("imperialAreaTimesUKImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, UKImperialVolumetricFlux>
) = volumetricFlux * this

@JvmName("imperialAreaTimesUSCustomaryArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, USCustomaryVolumetricFlux>
) = volumetricFlux * this

@JvmName("areaTimesVolumetricFlux")
infix operator fun <VolumetricFluxUnit : VolumetricFlux, AreaUnit : Area> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, VolumetricFluxUnit>
) = volumetricFlux * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.area

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.areaDensity.times
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialAreaDensity
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricAreaDensity
import com.splendo.kaluga.scientific.unit.UKImperialAreaDensity
import com.splendo.kaluga.scientific.unit.USCustomaryAreaDensity
import kotlin.jvm.JvmName

@JvmName("metricAreaTimesMetricAreaDenisty")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, MetricAreaDensity>
) = areaDensity * this

@JvmName("imperialAreaTimesImperialAreaDensity")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>
) = areaDensity * this

@JvmName("imperialAreaDensityTimesUKImperialAreaDensity")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>
) = areaDensity * this

@JvmName("imperialAreaTimesImperialUSCustomaryAreaDensity")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>
) = areaDensity * this

@JvmName("areaTimesAreaDensity")
infix operator fun <AreaDensityUnit : AreaDensity, AreaUnit : Area> ScientificValue<PhysicalQuantity.Area, AreaUnit>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>
) = areaDensity * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.areaDensity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.Length
import kotlin.jvm.JvmName

@JvmName("areaDensityFromDensityAndLengthDefault")
fun <
    DensityUnit : Density,
    LengthUnit : Length,
    AreaDensityUnit : AreaDensity
    > AreaDensityUnit.areaDensity(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = areaDensity(density, length, ::DefaultScientificValue)

@JvmName("areaDensityFromDensityAndLength")
fun <
    DensityUnit : Density,
    LengthUnit : Length,
    AreaDensityUnit : AreaDensity,
    Value : ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>
    > AreaDensityUnit.areaDensity(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    factory: (Decimal, AreaDensityUnit) -> Value
) = byMultiplying(density, length, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.areaDensity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("areaDensityFromLengthAndSpecificVolumeDefault")
fun <
    SpecificVolumeUnit : SpecificVolume,
    LengthUnit : Length,
    AreaDensityUnit : AreaDensity
    > AreaDensityUnit.areaDensity(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = areaDensity(length, specificVolume, ::DefaultScientificValue)

@JvmName("areaDensityFromLengthAndSpecificVolume")
fun <
    SpecificVolumeUnit : SpecificVolume,
    LengthUnit : Length,
    AreaDensityUnit : AreaDensity,
    Value : ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>
    > AreaDensityUnit.areaDensity(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    factory: (Decimal, AreaDensityUnit) -> Value
) = byDividing(length, specificVolume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.areaDensity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import kotlin.jvm.JvmName

@JvmName("areaDensityFromLinearMassDensityAndLengthDefault")
fun <
    AreaDensityUnit : AreaDensity,
    LengthUnit : Length,
    LinearMassDensityUnit : LinearMassDensity
    > AreaDensityUnit.areaDensity(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = areaDensity(linearMassDensity, length, ::DefaultScientificValue)

@JvmName("areaDensityFromLinearMassDensityAndLength")
fun <
    AreaDensityUnit : AreaDensity,
    LengthUnit : Length,
    LinearMassDensityUnit : LinearMassDensity,
    Value : ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>
    > AreaDensityUnit.areaDensity(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    factory: (Decimal, AreaDensityUnit) -> Value
) = byDividing(linearMassDensity, length, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.areaDensity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("areaDensityFromWeightAndAreaDefault")
fun <
    WeightUnit : Weight,
    AreaUnit : Area,
    AreaDensityUnit : AreaDensity
    > AreaDensityUnit.areaDensity(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = areaDensity(weight, area, ::DefaultScientificValue)

@JvmName("areaDensityFromWeightAndArea")
fun <
    WeightUnit : Weight,
    AreaUnit : Area,
    AreaDensityUnit : AreaDensity,
    Value : ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>
    > AreaDensityUnit.areaDensity(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, AreaDensityUnit) -> Value
) = byDividing(weight, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.areaDensity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.times
import com.splendo.kaluga.scientific.converter.density.density
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialAreaDensity
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.MetricAreaDensity
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.UKImperialAreaDensity
import com.splendo.kaluga.scientific.unit.USCustomaryAreaDensity
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricAreaDensityDivMetricLength")
infix operator fun <LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.AreaDensity, MetricAreaDensity>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) * 1(length.unit)).unit).density(this, length)

@JvmName("imperialAreaDensityDivImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) * 1(length.unit)).unit).density(this, length)

@JvmName("ukImperialAreaDensityDivImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) * 1(length.unit)).unit).density(this, length)

@JvmName("usCustomaryAreaDensityDivImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) * 1(length.unit)).unit).density(this, length)

@JvmName("areaDensityDivLength")
infix operator fun <AreaDensityUnit : AreaDensity, LengthUnit : Length> ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (Kilogram per CubicMeter).density(this, length)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.areaDensity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.length.length
import com.splendo.kaluga.scientific.converter.specificVolume.times
import com.splendo.kaluga.scientific.converter.volume.div
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialAreaDensity
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.MetricAreaDensity
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialAreaDensity
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomaryAreaDensity
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import kotlin.jvm.JvmName

@JvmName("metricAreaDensityTimesMetricSpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, MetricAreaDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>
) = specificVolume * this

@JvmName("imperialAreaDensityTimesImperialSpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = specificVolume * this

@JvmName("imperialAreaDensityTimesUKImperialSpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>
) = specificVolume * this

@JvmName("imperialAreaDensityTimesUSCustomarySpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>
) = specificVolume * this

@JvmName("ukImperialAreaDensityTimesImperialSpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = specificVolume * this

@JvmName("ukImperialAreaDensityTimesUKImperialSpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>
) = specificVolume * this

@JvmName("usCustomaryAreaDensityTimesImperialSpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = specificVolume * this

@JvmName("usCustomaryAreaDensityTimesUSCustomarySpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>
) = specificVolume * this

@JvmName("areaDensityTimesSpecificVolume")
infix operator fun <AreaDensityUnit : AreaDensity, SpecificVolumeUnit : SpecificVolume> ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = specificVolume * this

@JvmName("metricAreaDensityDivMetricDensity")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, MetricAreaDensity>.div(density: ScientificValue<PhysicalQuantity.Density, MetricDensity>) =
    (1(density.unit.per) / 1(unit.per)).unit.length(this, density)

@JvmName("imperialAreaDensityDivImperialDensity")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>.div(density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>) =
    (1(density.unit.per) / 1(unit.per)).unit.length(this, density)

@JvmName("imperialAreaDensityDivUKImperialDensity")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>.div(density: ScientificValue<PhysicalQuantity.Density, UKImperialDensity>) =
    (1(density.unit.per) / 1(unit.per)).unit.length(this, density)

@JvmName("imperialAreaDensityDivUSCustomaryDensity")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>.div(density: ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>) =
    (1(density.unit.per) / 1(unit.per)).unit.length(this, density)

@JvmName("ukImperialAreaDensityDivImperialDensity")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>.div(density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>) =
    (1(density.unit.per) / 1(unit.per)).unit.length(this, density)

@JvmName("ukImperialAreaDensityDivUKImperialDensity")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>.div(density: ScientificValue<PhysicalQuantity.Density, UKImperialDensity>) =
    (1(density.unit.per) / 1(unit.per)).unit.length(this, density)

@JvmName("usCustomaryAreaDensityDivImperialDensity")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>.div(density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>) =
    (1(density.unit.per) / 1(unit.per)).unit.length(this, density)

@JvmName("usCustomaryAreaDensityDivUSCustomaryDensity")
infix operator fun ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>.div(density: ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>) =
    (1(density.unit.per) / 1(unit.per)).unit.length(this, density)

@JvmName("areaDensityDivDensity")
infix operator fun <AreaDensityUnit : AreaDensity, DensityUnit : Density> ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = (1(density.unit.per) / 1(unit.per)).unit.length(this, density)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.areaDensity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.div
import com.splendo.kaluga.scientific.converter.linearMassDensity.linearMassDensity
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.ImperialAreaDensity
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricAreaDensity
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.UKImperialAreaDensity
import com.splendo.kaluga.scientific.unit.USCustomaryAreaDensity
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricAreaDensityTimesMetricLength")
infix operator fun <LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.AreaDensity, MetricAreaDensity>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) / length).unit).linearMassDensity(this, length)

@JvmName("imperialAreaDensityTimesImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) / length).unit).linearMassDensity(this, length)

@JvmName("ukImperialAreaDensityTimesImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) / length).unit).linearMassDensity(this, length)

@JvmName("usCustomaryAreaDensityTimesImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) / length).unit).linearMassDensity(this, length)

@JvmName("areaDensityTimesLength")
infix operator fun <AreaDensityUnit : AreaDensity, LengthUnit : Length> ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (Kilogram per Meter).linearMassDensity(this, length)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.areaDensity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.weight.mass
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialAreaDensity
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricAreaDensity
import com.splendo.kaluga.scientific.unit.UKImperialAreaDensity
import com.splendo.kaluga.scientific.unit.USCustomaryAreaDensity
import kotlin.jvm.JvmName

@JvmName("metricAreaDensityTimesMetricArea")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.AreaDensity, MetricAreaDensity>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = unit.weight.mass(this, area)

@JvmName("imperialAreaDensityTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = unit.weight.mass(this, area)

@JvmName("ukImperialAreaDensityTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = unit.weight.mass(this, area)

@JvmName("usCustomaryAreaDensityTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = unit.weight.mass(this, area)

@JvmName("areaDensityTimesArea")
infix operator fun <AreaDensityUnit : AreaDensity, AreaUnit : Area> ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Kilogram.mass(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.catalysticActivity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.CatalysticActivity
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("catalysticActivityFromAmountOfSubstanceAndTimeDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    TimeUnit : Time,
    CatalysisUnit : CatalysticActivity
    > CatalysisUnit.catalysticActivity(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = catalysticActivity(amountOfSubstance, time, ::DefaultScientificValue)

@JvmName("catalysticActivityFromAmountOfSubstanceAndTime")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    TimeUnit : Time,
    CatalysisUnit : CatalysticActivity,
    Value : ScientificValue<PhysicalQuantity.CatalysticActivity, CatalysisUnit>
    > CatalysisUnit.catalysticActivity(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, CatalysisUnit) -> Value
) = byDividing(amountOfSubstance, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.catalysticActivity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.amountOfSubstance.amountOfSubstance
import com.splendo.kaluga.scientific.unit.CatalysticActivity
import com.splendo.kaluga.scientific.unit.Mole
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("catalysticActivityTimesTime")
infix operator fun <TimeUnit : Time, CatalysisUnit : CatalysticActivity> ScientificValue<PhysicalQuantity.CatalysticActivity, CatalysisUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Mole.amountOfSubstance(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.decimal

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.frequency.frequency
import com.splendo.kaluga.scientific.unit.BeatsPerMinute
import com.splendo.kaluga.scientific.unit.Hertz
import com.splendo.kaluga.scientific.unit.Minute
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("numberDivMinute")
infix operator fun Number.div(minute: ScientificValue<PhysicalQuantity.Time, Minute>): ScientificValue<PhysicalQuantity.Frequency, BeatsPerMinute> =
    toDecimal() / minute

@JvmName("numberDivTime")
infix operator fun <TimeUnit : Time> Number.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>): ScientificValue<PhysicalQuantity.Frequency, Hertz> =
    toDecimal() / time

@JvmName("decimalDivMinute")
infix operator fun Decimal.div(minute: ScientificValue<PhysicalQuantity.Time, Minute>): ScientificValue<PhysicalQuantity.Frequency, BeatsPerMinute> =
    BeatsPerMinute.frequency(this, minute)

@JvmName("decimalDivTime")
infix operator fun <TimeUnit : Time> Decimal.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>): ScientificValue<PhysicalQuantity.Frequency, Hertz> =
    Hertz.frequency(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.decimal

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.radioactivity.radioactivity
import com.splendo.kaluga.scientific.unit.Becquerel
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("numberDivTime")
infix fun <TimeUnit : Time> Number.decaysPer(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>): ScientificValue<PhysicalQuantity.Radioactivity, Becquerel> =
    toDecimal().decaysPer(time)

@JvmName("decimalDivTime")
infix fun <TimeUnit : Time> Decimal.decaysPer(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>): ScientificValue<PhysicalQuantity.Radioactivity, Becquerel> =
    Becquerel.radioactivity(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.decimal

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.BeatsPerMinute
import com.splendo.kaluga.scientific.unit.Frequency
import com.splendo.kaluga.scientific.unit.Hour
import com.splendo.kaluga.scientific.unit.Radioactivity
import com.splendo.kaluga.scientific.unit.Second
import kotlin.jvm.JvmName

@JvmName("numberDivBPM")
infix operator fun Number.div(frequency: ScientificValue<PhysicalQuantity.Frequency, BeatsPerMinute>): ScientificValue<PhysicalQuantity.Time, Hour> =
    toDecimal() / frequency

@JvmName("numberDivFrequency")
infix operator fun <FrequencyUnit : Frequency> Number.div(frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>): ScientificValue<PhysicalQuantity.Time, Second> =
    toDecimal() / frequency

@JvmName("decimalDivBPM")
infix operator fun Decimal.div(frequency: ScientificValue<PhysicalQuantity.Frequency, BeatsPerMinute>): ScientificValue<PhysicalQuantity.Time, Hour> =
    Hour.time(this, frequency)

@JvmName("decimalDivFrequency")
infix operator fun <FrequencyUnit : Frequency> Decimal.div(frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>): ScientificValue<PhysicalQuantity.Time, Second> =
    Second.time(this, frequency)

@JvmName("numberDivRadioactivity")
infix operator fun <RadioactivityUnit : Radioactivity> Number.div(radioactivity: ScientificValue<PhysicalQuantity.Radioactivity, RadioactivityUnit>): ScientificValue<PhysicalQuantity.Time, Second> =
    toDecimal() / radioactivity

@JvmName("decimalDivRadioactivity")
infix operator fun <RadioactivityUnit : Radioactivity> Decimal.div(radioactivity: ScientificValue<PhysicalQuantity.Radioactivity, RadioactivityUnit>): ScientificValue<PhysicalQuantity.Time, Second> =
    Second.time(this, radioactivity)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.density

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.Length
import kotlin.jvm.JvmName

@JvmName("densityFromAreaDensityAndLengthDefault")
fun <
    DensityUnit : Density,
    LengthUnit : Length,
    AreaDensityUnit : AreaDensity
    > DensityUnit.density(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = density(areaDensity, length, ::DefaultScientificValue)

@JvmName("densityFromAreaDensityAndLength")
fun <
    DensityUnit : Density,
    LengthUnit : Length,
    AreaDensityUnit : AreaDensity,
    Value : ScientificValue<PhysicalQuantity.Density, DensityUnit>
    > DensityUnit.density(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    factory: (Decimal, DensityUnit) -> Value
) = byDividing(areaDensity, length, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.density

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import kotlin.jvm.JvmName

@JvmName("densityFromLinearMassDensityAndAreaDefault")
fun <
    DensityUnit : Density,
    AreaUnit : Area,
    LinearMassDensityUnit : LinearMassDensity
    > DensityUnit.density(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = density(linearMassDensity, area, ::DefaultScientificValue)

@JvmName("densityFromLinearMassDensityAndArea")
fun <
    DensityUnit : Density,
    AreaUnit : Area,
    LinearMassDensityUnit : LinearMassDensity,
    Value : ScientificValue<PhysicalQuantity.Density, DensityUnit>
    > DensityUnit.density(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, DensityUnit) -> Value
) = byDividing(linearMassDensity, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.density

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.MolarVolume
import kotlin.jvm.JvmName

@JvmName("densityFromMolarMassAndMolarVolumeDefault")
fun <
    MolarMassUnit : MolarMass,
    MolarVolumeUnit : MolarVolume,
    DensityUnit : Density
    > DensityUnit.density(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
) = density(molarMass, molarVolume, ::DefaultScientificValue)

@JvmName("densityFromMolarMassAndMolarVolume")
fun <
    MolarMassUnit : MolarMass,
    MolarVolumeUnit : MolarVolume,
    DensityUnit : Density,
    Value : ScientificValue<PhysicalQuantity.Density, DensityUnit>
    > DensityUnit.density(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>,
    factory: (Decimal, DensityUnit) -> Value
) = byDividing(molarMass, molarVolume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.density

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.Molarity
import kotlin.jvm.JvmName

@JvmName("densityFromMolarityAndMolalityDefault")
fun <
    MolarityUnit : Molarity,
    MolalityUnit : Molality,
    DensityUnit : Density
    > DensityUnit.density(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>,
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = density(molarity, molality, ::DefaultScientificValue)

@JvmName("densityFromMolarityAndMolality")
fun <
    MolarityUnit : Molarity,
    MolalityUnit : Molality,
    DensityUnit : Density,
    Value : ScientificValue<PhysicalQuantity.Density, DensityUnit>
    > DensityUnit.density(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>,
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>,
    factory: (Decimal, DensityUnit) -> Value
) = byDividing(molarity, molality, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.density

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.Molarity
import kotlin.jvm.JvmName

@JvmName("densityFromMolarityAndMolarMassDefault")
fun <
    DensityUnit : Density,
    MolarityUnit : Molarity,
    MolarMassUnit : MolarMass
    > DensityUnit.density(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = density(molarMass, molarity, ::DefaultScientificValue)

@JvmName("densityFromMolarityAndMolarMass")
fun <
    DensityUnit : Density,
    MolarityUnit : Molarity,
    MolarMassUnit : MolarMass,
    Value : ScientificValue<PhysicalQuantity.Density, DensityUnit>
    > DensityUnit.density(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>,
    factory: (Decimal, DensityUnit) -> Value
) = byMultiplying(molarMass, molarity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.density

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byInverting
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("densityFromInverseSpecificVolumeDefault")
fun <
    SpecificVolumeUnit : SpecificVolume,
    DensityUnit : Density
    > DensityUnit.density(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = density(specificVolume, ::DefaultScientificValue)

@JvmName("densityFromInverseSpecificVolume")
fun <
    SpecificVolumeUnit : SpecificVolume,
    DensityUnit : Density,
    Value : ScientificValue<PhysicalQuantity.Density, DensityUnit>
    > DensityUnit.density(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    factory: (Decimal, DensityUnit) -> Value
) = byInverting(specificVolume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.density

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("densityFromWeightAndVolumeDefault")
fun <
    WeightUnit : Weight,
    VolumeUnit : Volume,
    DensityUnit : Density
    > DensityUnit.density(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = density(weight, volume, ::DefaultScientificValue)

@JvmName("densityFromWeightAndVolume")
fun <
    WeightUnit : Weight,
    VolumeUnit : Volume,
    DensityUnit : Density,
    Value : ScientificValue<PhysicalQuantity.Density, DensityUnit>
    > DensityUnit.density(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    factory: (Decimal, DensityUnit) -> Value
) = byDividing(weight, volume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.density

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.areaDensity.areaDensity
import com.splendo.kaluga.scientific.converter.volume.div
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricDensityTimesMetricLength")
infix operator fun <LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.Density, MetricDensity>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) / length).unit).areaDensity(this, length)

@JvmName("imperialDensityTimesImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Density, ImperialDensity>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) / length).unit).areaDensity(this, length)

@JvmName("ukImperialDensityTimesImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Density, UKImperialDensity>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) / length).unit).areaDensity(this, length)

@JvmName("usCustomaryDensityTimesImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) / length).unit).areaDensity(this, length)

@JvmName("densityTimesLength")
infix operator fun <DensityUnit : Density, LengthUnit : Length> ScientificValue<PhysicalQuantity.Density, DensityUnit>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (Kilogram per SquareMeter).areaDensity(this, length)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.density

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.linearMassDensity.linearMassDensity
import com.splendo.kaluga.scientific.converter.volume.div
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricDensityTimesMetricArea")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.Density, MetricDensity>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit.weight per (1(unit.per) / area).unit).linearMassDensity(this, area)

@JvmName("imperialDensityTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Density, ImperialDensity>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit.weight per (1(unit.per) / area).unit).linearMassDensity(this, area)

@JvmName("ukImperialDensityTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Density, UKImperialDensity>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit.weight per (1(unit.per) / area).unit).linearMassDensity(this, area)

@JvmName("usCustomaryDensityTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit.weight per (1(unit.per) / area).unit).linearMassDensity(this, area)

@JvmName("densityTimesArea")
infix operator fun <DensityUnit : Density, AreaUnit : Area> ScientificValue<PhysicalQuantity.Density, DensityUnit>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (Kilogram per Meter).linearMassDensity(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.density

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarMass.molarMass
import com.splendo.kaluga.scientific.converter.molarVolume.times
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricDensityDivMolarity")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.Density, MetricDensity>.div(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = (unit.weight per molarity.unit.amountOfSubstance).molarMass(this, molarity)

@JvmName("imperialDensityDivMolarity")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.Density, ImperialDensity>.div(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = (unit.weight per molarity.unit.amountOfSubstance).molarMass(this, molarity)

@JvmName("ukImperialDensityDivMolarity")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.Density, UKImperialDensity>.div(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = (unit.weight per molarity.unit.amountOfSubstance).molarMass(this, molarity)

@JvmName("usCustomaryDensityDivMolarity")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>.div(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = (unit.weight per molarity.unit.amountOfSubstance).molarMass(this, molarity)

@JvmName("densityDivMolarity")
infix operator fun <DensityUnit : Density, MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.Density, DensityUnit>.div(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = (Kilogram per molarity.unit.amountOfSubstance).molarMass(this, molarity)

@JvmName("metricDensityTimesMolarVolume")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.Density, MetricDensity>.times(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>) =
    molarVolume * this

@JvmName("imperialDensityTimesMolarVolume")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.Density, ImperialDensity>.times(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>) =
    molarVolume * this

@JvmName("ukImperialDensityTimesMolarVolume")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.Density, UKImperialDensity>.times(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>) =
    molarVolume * this

@JvmName("usCustomaryDensityTimesMolarVolume")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>.times(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>) =
    molarVolume * this

@JvmName("densityTimesMolarVolume")
infix operator fun <DensityUnit : Density, MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.Density, DensityUnit>.times(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
) = molarVolume * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.density

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molality.times
import com.splendo.kaluga.scientific.converter.molarity.molarity
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricDensityTimesMolality")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.Density, MetricDensity>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = molality * this

@JvmName("imperialDensityTimesMolality")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.Density, ImperialDensity>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = molality * this

@JvmName("ukImperialDensityTimesMolality")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.Density, UKImperialDensity>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = molality * this

@JvmName("usCustomaryDensityTimesMolality")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = molality * this

@JvmName("densityTimesMolality")
infix operator fun <MolalityUnit : Molality, DensityUnit : Density> ScientificValue<PhysicalQuantity.Density, DensityUnit>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = molality * this

@JvmName("metricDensityDivMolarMass")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.Density, MetricDensity>.div(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = (molarMass.unit.per per unit.per).molarity(this, molarMass)

@JvmName("imperialDensityDivMolarMass")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.Density, ImperialDensity>.div(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = (molarMass.unit.per per unit.per).molarity(this, molarMass)

@JvmName("ukImperialDensityDivMolarMass")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.Density, UKImperialDensity>.div(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = (molarMass.unit.per per unit.per).molarity(this, molarMass)

@JvmName("usCustomaryDensityDivMolarMass")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>.div(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = (molarMass.unit.per per unit.per).molarity(this, molarMass)

@JvmName("densityDivMolarMass")
infix operator fun <DensityUnit : Density, MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.Density, DensityUnit>.div(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = (molarMass.unit.per per CubicMeter).molarity(this, molarMass)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.density

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.specificVolume.specificVolume
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricDensitySpecificVolume")
fun ScientificValue<PhysicalQuantity.Density, MetricDensity>.specificVolume() =
    (unit.per per unit.weight).specificVolume(this)

@JvmName("imperialDensitySpecificVolume")
fun ScientificValue<PhysicalQuantity.Density, ImperialDensity>.specificVolume() =
    (unit.per per unit.weight).specificVolume(this)

@JvmName("ukImperialDensitySpecificVolume")
fun ScientificValue<PhysicalQuantity.Density, UKImperialDensity>.specificVolume() =
    (unit.per per unit.weight).specificVolume(this)

@JvmName("usCustomaryDensitySpecificVolume")
fun ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>.specificVolume() =
    (unit.per per unit.weight).specificVolume(this)

@JvmName("densitySpecificVolume")
fun <DensityUnit : Density> ScientificValue<PhysicalQuantity.Density, DensityUnit>.specificVolume() =
    (CubicMeter per Kilogram).specificVolume(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.density

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.weight.mass
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.ImperialVolume
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.MetricVolume
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.UKImperialVolume
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.USCustomaryVolume
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("metricDensityTimesMetricVolume")
infix operator fun <VolumeUnit : MetricVolume> ScientificValue<PhysicalQuantity.Density, MetricDensity>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = unit.weight.mass(this, volume)

@JvmName("imperialDensityTimesImperialVolume")
infix operator fun <VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Density, ImperialDensity>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = unit.weight.mass(this, volume)

@JvmName("imperialDensityTimesUKImperialVolume")
infix operator fun <VolumeUnit : UKImperialVolume> ScientificValue<PhysicalQuantity.Density, ImperialDensity>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = unit.weight.mass(this, volume)

@JvmName("imperialDensityTimesUSCustomaryVolume")
infix operator fun <VolumeUnit : USCustomaryVolume> ScientificValue<PhysicalQuantity.Density, ImperialDensity>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = unit.weight.mass(this, volume)

@JvmName("ukImperialDensityTimesImperialVolume")
infix operator fun <VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Density, UKImperialDensity>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = unit.weight.mass(this, volume)

@JvmName("ukImperialDensityTimesUKImperialVolume")
infix operator fun <VolumeUnit : UKImperialVolume> ScientificValue<PhysicalQuantity.Density, UKImperialDensity>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = unit.weight.mass(this, volume)

@JvmName("usCustomaryDensityTimesUKImperialVolume")
infix operator fun <VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = unit.weight.mass(this, volume)

@JvmName("usCustomaryDensityTimesUSCustomaryVolume")
infix operator fun <VolumeUnit : USCustomaryVolume> ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = unit.weight.mass(this, volume)

@JvmName("densityTimesVolume")
infix operator fun <DensityUnit : Density, VolumeUnit : Volume> ScientificValue<PhysicalQuantity.Density, DensityUnit>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = Kilogram.mass(this, volume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.dynamicViscosity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.DynamicViscosity
import com.splendo.kaluga.scientific.unit.Momentum
import kotlin.jvm.JvmName

@JvmName("dynamicViscosityFromMomentumAndAreaDefault")
fun <
    MomentumUnit : Momentum,
    AreaUnit : Area,
    DynamicViscosityUnit : DynamicViscosity
    > DynamicViscosityUnit.dynamicViscosity(
    momentum: ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = dynamicViscosity(momentum, area, ::DefaultScientificValue)

@JvmName("dynamicViscosityFromMomentumAndArea")
fun <
    MomentumUnit : Momentum,
    AreaUnit : Area,
    DynamicViscosityUnit : DynamicViscosity,
    Value : ScientificValue<PhysicalQuantity.DynamicViscosity, DynamicViscosityUnit>
    > DynamicViscosityUnit.dynamicViscosity(
    momentum: ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, DynamicViscosityUnit) -> Value
) = byDividing(momentum, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.dynamicViscosity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.DynamicViscosity
import com.splendo.kaluga.scientific.unit.Pressure
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("dynamicViscosityFromPressureAndTimeDefault")
fun <
    DynamicViscosityUnit : DynamicViscosity,
    TimeUnit : Time,
    PressureUnit : Pressure
    > DynamicViscosityUnit.dynamicViscosity(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = dynamicViscosity(pressure, time, ::DefaultScientificValue)

@JvmName("dynamicViscosityFromPressureAndTime")
fun <
    DynamicViscosityUnit : DynamicViscosity,
    TimeUnit : Time,
    PressureUnit : Pressure,
    Value : ScientificValue<PhysicalQuantity.DynamicViscosity, DynamicViscosityUnit>
    > DynamicViscosityUnit.dynamicViscosity(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, DynamicViscosityUnit) -> Value
) = byMultiplying(pressure, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.dynamicViscosity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.force.times
import com.splendo.kaluga.scientific.converter.momentum.momentum
import com.splendo.kaluga.scientific.converter.pressure.times
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.DynamicViscosity
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialDynamicViscosity
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricDynamicViscosity
import com.splendo.kaluga.scientific.unit.UKImperialDynamicViscosity
import com.splendo.kaluga.scientific.unit.USCustomaryDynamicViscosity
import kotlin.jvm.JvmName

@JvmName("metricDynamicViscosityTimesMetricArea")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.DynamicViscosity, MetricDynamicViscosity>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = ((1(unit.pressure) * area) * 1(unit.time)).unit.momentum(this, area)

@JvmName("imperialDynamicViscosityTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.DynamicViscosity, ImperialDynamicViscosity>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = ((1(unit.pressure) * area) * 1(unit.time)).unit.momentum(this, area)

@JvmName("ukImperialDynamicViscosityTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.DynamicViscosity, UKImperialDynamicViscosity>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = ((1(unit.pressure) * area) * 1(unit.time)).unit.momentum(this, area)

@JvmName("usCustomaryDynamicViscosityTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.DynamicViscosity, USCustomaryDynamicViscosity>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = ((1(unit.pressure) * area) * 1(unit.time)).unit.momentum(this, area)

@JvmName("dynamicViscosityTimesArea")
infix operator fun <DynamicViscosityUnit : DynamicViscosity, AreaUnit : Area> ScientificValue<PhysicalQuantity.DynamicViscosity, DynamicViscosityUnit>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = ((1(unit.pressure) * area) * 1(unit.time)).unit.momentum(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.dynamicViscosity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.pressure.pressure
import com.splendo.kaluga.scientific.unit.DynamicViscosity
import com.splendo.kaluga.scientific.unit.ImperialDynamicViscosity
import com.splendo.kaluga.scientific.unit.MetricDynamicViscosity
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.UKImperialDynamicViscosity
import com.splendo.kaluga.scientific.unit.USCustomaryDynamicViscosity
import kotlin.jvm.JvmName

@JvmName("metricDynamicViscosityDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.DynamicViscosity, MetricDynamicViscosity>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit.pressure).pressure(this, time)

@JvmName("imperialDynamicViscosityDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.DynamicViscosity, ImperialDynamicViscosity>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit.pressure).pressure(this, time)

@JvmName("ukImperialDynamicViscosityDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.DynamicViscosity, UKImperialDynamicViscosity>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit.pressure).pressure(this, time)

@JvmName("usCustomaryDynamicViscosityDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.DynamicViscosity, USCustomaryDynamicViscosity>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit.pressure).pressure(this, time)

@JvmName("dynamicViscosityDivTime")
infix operator fun <DynamicViscosityUnit : DynamicViscosity, TimeUnit : Time> ScientificValue<PhysicalQuantity.DynamicViscosity, DynamicViscosityUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit.pressure).pressure(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.dynamicViscosity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.DynamicViscosity
import com.splendo.kaluga.scientific.unit.Pressure
import kotlin.jvm.JvmName

@JvmName("dynamicViscosityDivPressure")
infix operator fun <DynamicViscosityUnit : DynamicViscosity, PressureUnit : Pressure> ScientificValue<PhysicalQuantity.DynamicViscosity, DynamicViscosityUnit>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = unit.time.time(this, pressure)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCapacitance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("capacitanceFromChargeAndVoltageDefault")
fun <
    ChargeUnit : ElectricCharge,
    VoltageUnit : Voltage,
    CapacitanceUnit : ElectricCapacitance
    > CapacitanceUnit.capacitance(
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = capacitance(charge, voltage, ::DefaultScientificValue)

@JvmName("capacitanceFromChargeAndVoltage")
fun <
    ChargeUnit : ElectricCharge,
    VoltageUnit : Voltage,
    CapacitanceUnit : ElectricCapacitance,
    Value : ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>
    > CapacitanceUnit.capacitance(
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    factory: (Decimal, CapacitanceUnit) -> Value
) = byDividing(charge, voltage, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCapacitance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.ElectricConductance
import com.splendo.kaluga.scientific.unit.Frequency
import kotlin.jvm.JvmName

@JvmName("capacitanceFromConductanceAndFrequencyDefault")
fun <
    CapacitanceUnit : ElectricCapacitance,
    ConductanceUnit : ElectricConductance,
    FrequencyUnit : Frequency
    > CapacitanceUnit.capacitance(
    conductance: ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>,
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
) = capacitance(conductance, frequency, ::DefaultScientificValue)

@JvmName("capacitanceFromConductanceAndFrequency")
fun <
    CapacitanceUnit : ElectricCapacitance,
    ConductanceUnit : ElectricConductance,
    FrequencyUnit : Frequency,
    Value : ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>
    > CapacitanceUnit.capacitance(
    conductance: ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>,
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>,
    factory: (Decimal, CapacitanceUnit) -> Value
) = byDividing(conductance, frequency, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCapacitance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("capacitanceFromTimeAndResistanceDefault")
fun <
    CapacitanceUnit : ElectricCapacitance,
    TimeUnit : Time,
    ResistanceUnit : ElectricResistance
    > CapacitanceUnit.capacitance(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
) = capacitance(time, resistance, ::DefaultScientificValue)

@JvmName("capacitanceFromTimeAndResistance")
fun <
    CapacitanceUnit : ElectricCapacitance,
    TimeUnit : Time,
    ResistanceUnit : ElectricResistance,
    Value : ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>
    > CapacitanceUnit.capacitance(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>,
    factory: (Decimal, CapacitanceUnit) -> Value
) = byDividing(time, resistance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCapacitance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCharge.charge
import com.splendo.kaluga.scientific.unit.Abcoulomb
import com.splendo.kaluga.scientific.unit.Abfarad
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.Coulomb
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("abfaradTimesAbvolt")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCapacitance, Abfarad>.times(voltage: ScientificValue<PhysicalQuantity.Voltage, Abvolt>) =
    Abcoulomb.charge(this, voltage)

@JvmName("capacitanceTimesVoltage")
infix operator fun <CapacitanceUnit : ElectricCapacitance, VoltageUnit : Voltage> ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>.times(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = Coulomb.charge(this, voltage)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCapacitance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricConductance.conductance
import com.splendo.kaluga.scientific.unit.Abfarad
import com.splendo.kaluga.scientific.unit.Absiemens
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.Frequency
import com.splendo.kaluga.scientific.unit.Siemens
import kotlin.jvm.JvmName

@JvmName("abfaradTimesFrequency")
infix operator fun <FrequencyUnit : Frequency> ScientificValue<PhysicalQuantity.ElectricCapacitance, Abfarad>.times(
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
) = Absiemens.conductance(this, frequency)

@JvmName("capacitanceTimesFrequency")
infix operator fun <CapacitanceUnit : ElectricCapacitance, FrequencyUnit : Frequency> ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>.times(
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
) = Siemens.conductance(this, frequency)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCapacitance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.duration
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Second
import kotlin.jvm.JvmName

@JvmName("capacitanceTimesResistance")
infix operator fun <CapacitanceUnit : ElectricCapacitance, ResistanceUnit : ElectricResistance> ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>.times(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
) = Second.duration(this, resistance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCharge

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("chargeFromCapacitanceAndVoltageDefault")
fun <
    ChargeUnit : ElectricCharge,
    VoltageUnit : Voltage,
    CapacitanceUnit : ElectricCapacitance
    > ChargeUnit.charge(
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = charge(capacitance, voltage, ::DefaultScientificValue)

@JvmName("chargeFromCapacitanceAndVoltage")
fun <
    ChargeUnit : ElectricCharge,
    VoltageUnit : Voltage,
    CapacitanceUnit : ElectricCapacitance,
    Value : ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>
    > ChargeUnit.charge(
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    factory: (Decimal, ChargeUnit) -> Value
) = byMultiplying(capacitance, voltage, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCharge

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("chargeFromCurrentAndTimeDefault")
fun <
    CurrentUnit : ElectricCurrent,
    TimeUnit : Time,
    ChargeUnit : ElectricCharge
    > ChargeUnit.charge(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = charge(current, time, ::DefaultScientificValue)

@JvmName("chargeFromCurrentAndTime")
fun <
    CurrentUnit : ElectricCurrent,
    TimeUnit : Time,
    ChargeUnit : ElectricCharge,
    Value : ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>
    > ChargeUnit.charge(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, ChargeUnit) -> Value
) = byMultiplying(current, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCharge

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("chargeFromEnergyAndVoltageDefault")
fun <
    VoltageUnit : Voltage,
    EnergyUnit : Energy,
    ChargeUnit : ElectricCharge
    > ChargeUnit.charge(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = charge(energy, voltage, ::DefaultScientificValue)

@JvmName("chargeFromEnergyAndVoltage")
fun <
    VoltageUnit : Voltage,
    EnergyUnit : Energy,
    ChargeUnit : ElectricCharge,
    Value : ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>
    > ChargeUnit.charge(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    factory: (Decimal, ChargeUnit) -> Value
) = byDividing(energy, voltage, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCharge

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.MagneticFlux
import kotlin.jvm.JvmName

@JvmName("chargeFromFluxAndResistanceDefault")
fun <
    ResistanceUnit : ElectricResistance,
    ChargeUnit : ElectricCharge,
    FluxUnit : MagneticFlux
    > ChargeUnit.charge(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
) = charge(flux, resistance, ::DefaultScientificValue)

@JvmName("chargeFromFluxAndResistance")
fun <
    ResistanceUnit : ElectricResistance,
    ChargeUnit : ElectricCharge,
    FluxUnit : MagneticFlux,
    Value : ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>
    > ChargeUnit.charge(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>,
    factory: (Decimal, ChargeUnit) -> Value
) = byDividing(flux, resistance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCharge

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCapacitance.capacitance
import com.splendo.kaluga.scientific.unit.Abcoulomb
import com.splendo.kaluga.scientific.unit.Abfarad
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.Farad
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("abcoulombDivAbVolt")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCharge, Abcoulomb>.div(voltage: ScientificValue<PhysicalQuantity.Voltage, Abvolt>) =
    Abfarad.capacitance(this, voltage)

@JvmName("chargeDivVoltage")
infix operator fun <ChargeUnit : ElectricCharge, VoltageUnit : Voltage> ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>.div(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = Farad.capacitance(this, voltage)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCharge

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCurrent.current
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Abcoulomb
import com.splendo.kaluga.scientific.unit.Ampere
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("abcoulombDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.ElectricCharge, Abcoulomb>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Abampere.current(this, time)

@JvmName("chargeDivTime")
infix operator fun <ChargeUnit : ElectricCharge, TimeUnit : Time> ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Ampere.current(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCharge

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.energy.energy
import com.splendo.kaluga.scientific.unit.Abcoulomb
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("abcoulombTimesAbvolt")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCharge, Abcoulomb>.times(voltage: ScientificValue<PhysicalQuantity.Voltage, Abvolt>) =
    Erg.energy(this, voltage)

@JvmName("chargeTimesVoltage")
infix operator fun <ChargeUnit : ElectricCharge, VoltageUnit : Voltage> ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>.times(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = Joule.energy(this, voltage)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCharge

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricResistance.times
import com.splendo.kaluga.scientific.unit.Abcoulomb
import com.splendo.kaluga.scientific.unit.Abohm
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.ElectricResistance
import kotlin.jvm.JvmName

@JvmName("abcoulombTimesAbohm")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCharge, Abcoulomb>.times(resistance: ScientificValue<PhysicalQuantity.ElectricResistance, Abohm>) =
    resistance * this

@JvmName("chargeTimesResistance")
infix operator fun <ResistanceUnit : ElectricResistance, ChargeUnit : ElectricCharge> ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>.times(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
) = resistance * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCharge

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.duration
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Second
import kotlin.jvm.JvmName

@JvmName("chargeDivCurrent")
infix operator fun <ChargeUnit : ElectricCharge, CurrentUnit : ElectricCurrent> ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>.div(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
) = Second.duration(this, current)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCharge

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.voltage.voltage
import com.splendo.kaluga.scientific.unit.Abcoulomb
import com.splendo.kaluga.scientific.unit.Abfarad
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.Volt
import kotlin.jvm.JvmName

@JvmName("abcoulombDivAbfarad")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCharge, Abcoulomb>.div(capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, Abfarad>) =
    Abvolt.voltage(this, capacitance)

@JvmName("chargeDivCapacitance")
infix operator fun <ChargeUnit : ElectricCharge, CapacitanceUnit : ElectricCapacitance> ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>.div(
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>
) = Volt.voltage(this, capacitance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricConductance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.ElectricConductance
import com.splendo.kaluga.scientific.unit.Frequency
import kotlin.jvm.JvmName

@JvmName("conductanceFromCapacitanceAndFrequencyDefault")
fun <
    CapacitanceUnit : ElectricCapacitance,
    ConductanceUnit : ElectricConductance,
    FrequencyUnit : Frequency
    > ConductanceUnit.conductance(
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>,
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
) = conductance(capacitance, frequency, ::DefaultScientificValue)

@JvmName("conductanceFromCapacitanceAndFrequency")
fun <
    CapacitanceUnit : ElectricCapacitance,
    ConductanceUnit : ElectricConductance,
    FrequencyUnit : Frequency,
    Value : ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>
    > ConductanceUnit.conductance(
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>,
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>,
    factory: (Decimal, ConductanceUnit) -> Value
) = byMultiplying(capacitance, frequency, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricConductance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricConductance
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("conductanceFromCurrentAndVoltageDefault")
fun <
    CurrentUnit : ElectricCurrent,
    VoltageUnit : Voltage,
    ConductanceUnit : ElectricConductance
    > ConductanceUnit.conductance(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = conductance(current, voltage, ::DefaultScientificValue)

@JvmName("conductanceFromCurrentAndVoltage")
fun <
    CurrentUnit : ElectricCurrent,
    VoltageUnit : Voltage,
    ConductanceUnit : ElectricConductance,
    Value : ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>
    > ConductanceUnit.conductance(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    factory: (Decimal, ConductanceUnit) -> Value
) = byDividing(current, voltage, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricConductance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byInverting
import com.splendo.kaluga.scientific.unit.ElectricConductance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import kotlin.jvm.JvmName

@JvmName("electricConductanceFromInvertedResistanceDefault")
fun <
    ResistanceUnit : ElectricResistance,
    ConductanceUnit : ElectricConductance
    > ConductanceUnit.conductance(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
) = conductance(resistance, ::DefaultScientificValue)

@JvmName("electricConductanceFromInvertedResistance")
fun <
    ResistanceUnit : ElectricResistance,
    ConductanceUnit : ElectricConductance,
    Value : ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>
    > ConductanceUnit.conductance(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>,
    factory: (Decimal, ConductanceUnit) -> Value
) = byInverting(resistance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricConductance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCapacitance.capacitance
import com.splendo.kaluga.scientific.unit.Abfarad
import com.splendo.kaluga.scientific.unit.Absiemens
import com.splendo.kaluga.scientific.unit.ElectricConductance
import com.splendo.kaluga.scientific.unit.Farad
import com.splendo.kaluga.scientific.unit.Frequency
import kotlin.jvm.JvmName

@JvmName("absiemensDivFrequency")
infix operator fun <FrequencyUnit : Frequency> ScientificValue<PhysicalQuantity.ElectricConductance, Absiemens>.div(
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
) = Abfarad.capacitance(this, frequency)

@JvmName("conductanceDivFrequency")
infix operator fun <ConductanceUnit : ElectricConductance, FrequencyUnit : Frequency> ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>.div(
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
) = Farad.capacitance(this, frequency)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricConductance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCurrent.current
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Absiemens
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.Ampere
import com.splendo.kaluga.scientific.unit.ElectricConductance
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("absiemensTimesAbvolt")
infix operator fun ScientificValue<PhysicalQuantity.ElectricConductance, Absiemens>.times(voltage: ScientificValue<PhysicalQuantity.Voltage, Abvolt>) =
    Abampere.current(this, voltage)

@JvmName("conductanceTimesVoltage")
infix operator fun <ConductanceUnit : ElectricConductance, VoltageUnit : Voltage> ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>.times(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = Ampere.current(this, voltage)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricConductance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricResistance.resistance
import com.splendo.kaluga.scientific.unit.Abohm
import com.splendo.kaluga.scientific.unit.Absiemens
import com.splendo.kaluga.scientific.unit.ElectricConductance
import com.splendo.kaluga.scientific.unit.Ohm
import kotlin.jvm.JvmName

@JvmName("abSiemensResistance")
fun ScientificValue<PhysicalQuantity.ElectricConductance, Absiemens>.resistance() =
    Abohm.resistance(this)

@JvmName("resistance")
fun <ConductanceUnit : ElectricConductance> ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>.resistance() =
    Ohm.resistance(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricConductance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.frequency.frequency
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.ElectricConductance
import com.splendo.kaluga.scientific.unit.Hertz
import kotlin.jvm.JvmName

@JvmName("conductanceDivCapacitance")
infix operator fun <ConductanceUnit : ElectricConductance, CapacitanceUnit : ElectricCapacitance> ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>.div(
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>
) = Hertz.frequency(this, capacitance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCurrent

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("currentFromChargeAndTimeDefault")
fun <
    CurrentUnit : ElectricCurrent,
    TimeUnit : Time,
    ChargeUnit : ElectricCharge
    > CurrentUnit.current(
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>,
    per: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = current(charge, per, ::DefaultScientificValue)

@JvmName("currentFromChargeAndTime")
fun <
    CurrentUnit : ElectricCurrent,
    TimeUnit : Time,
    ChargeUnit : ElectricCharge,
    Value : ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
    > CurrentUnit.current(
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>,
    per: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, CurrentUnit) -> Value
) = byDividing(charge, per, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCurrent

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.MagneticFlux
import kotlin.jvm.JvmName

@JvmName("currentFromEnergyAndFluxDefault")
fun <
    EnergyUnit : Energy,
    CurrentUnit : ElectricCurrent,
    FluxUnit : MagneticFlux
    > CurrentUnit.current(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>
) = current(energy, flux, ::DefaultScientificValue)

@JvmName("currentFromEnergyAndFlux")
fun <
    EnergyUnit : Energy,
    CurrentUnit : ElectricCurrent,
    FluxUnit : MagneticFlux,
    Value : ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
    > CurrentUnit.current(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    factory: (Decimal, CurrentUnit) -> Value
) = byDividing(energy, flux, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCurrent

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.ElectricInductance
import com.splendo.kaluga.scientific.unit.MagneticFlux
import kotlin.jvm.JvmName

@JvmName("currentFromFluxAndInductanceDefault")
fun <
    FluxUnit : MagneticFlux,
    CurrentUnit : ElectricCurrent,
    InductanceUnit : ElectricInductance
    > CurrentUnit.current(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>
) = current(flux, inductance, ::DefaultScientificValue)

@JvmName("currentFromFluxAndInductance")
fun <
    FluxUnit : MagneticFlux,
    CurrentUnit : ElectricCurrent,
    InductanceUnit : ElectricInductance,
    Value : ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
    > CurrentUnit.current(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>,
    factory: (Decimal, CurrentUnit) -> Value
) = byDividing(flux, inductance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCurrent

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("currentFromPowerAndVoltageDefault")
fun <
    VoltageUnit : Voltage,
    ElectricCurrentUnit : ElectricCurrent,
    PowerUnit : Power
    > ElectricCurrentUnit.current(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = current(power, voltage, ::DefaultScientificValue)

@JvmName("currentFromPowerAndVoltage")
fun <
    VoltageUnit : Voltage,
    ElectricCurrentUnit : ElectricCurrent,
    PowerUnit : Power,
    Value : ScientificValue<PhysicalQuantity.ElectricCurrent, ElectricCurrentUnit>
    > ElectricCurrentUnit.current(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    factory: (Decimal, ElectricCurrentUnit) -> Value
) = byDividing(power, voltage, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCurrent

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.ElectricConductance
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("currentFromVoltageAndConductanceDefault")
fun <
    CurrentUnit : ElectricCurrent,
    VoltageUnit : Voltage,
    ConductanceUnit : ElectricConductance
    > CurrentUnit.current(
    conductance: ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = current(conductance, voltage, ::DefaultScientificValue)

@JvmName("currentFromVoltageAndConductance")
fun <
    CurrentUnit : ElectricCurrent,
    VoltageUnit : Voltage,
    ConductanceUnit : ElectricConductance,
    Value : ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
    > CurrentUnit.current(
    conductance: ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    factory: (Decimal, CurrentUnit) -> Value
) = byMultiplying(conductance, voltage, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCurrent

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("currentFromVoltageAndResistanceDefault")
fun <
    CurrentUnit : ElectricCurrent,
    VoltageUnit : Voltage,
    ResistanceUnit : ElectricResistance
    > CurrentUnit.current(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
) = current(voltage, resistance, ::DefaultScientificValue)

@JvmName("currentFromVoltageAndResistance")
fun <
    CurrentUnit : ElectricCurrent,
    VoltageUnit : Voltage,
    ResistanceUnit : ElectricResistance,
    Value : ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
    > CurrentUnit.current(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>,
    factory: (Decimal, CurrentUnit) -> Value
) = byDividing(voltage, resistance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCurrent

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCharge.charge
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Abcoulomb
import com.splendo.kaluga.scientific.unit.Biot
import com.splendo.kaluga.scientific.unit.Coulomb
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("abampereTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Abcoulomb.charge(this, time)

@JvmName("biotTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Abcoulomb.charge(this, time)

@JvmName("currentTimesTime")
infix operator fun <CurrentUnit : ElectricCurrent, TimeUnit : Time> ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Coulomb.charge(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCurrent

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricConductance.conductance
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Absiemens
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.Biot
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Siemens
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("abampereDivAbvolt")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>.div(voltage: ScientificValue<PhysicalQuantity.Voltage, Abvolt>) =
    Absiemens.conductance(this, voltage)

@JvmName("biotDivAbvolt")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>.div(voltage: ScientificValue<PhysicalQuantity.Voltage, Abvolt>) =
    Absiemens.conductance(this, voltage)

@JvmName("currentDivVoltage")
infix operator fun <CurrentUnit : ElectricCurrent, VoltageUnit : Voltage> ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>.div(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = Siemens.conductance(this, voltage)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCurrent

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.magneticFlux.times
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Biot
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.Maxwell
import kotlin.jvm.JvmName

@JvmName("abampereTimesMaxwell")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>.times(flux: ScientificValue<PhysicalQuantity.MagneticFlux, Maxwell>) =
    flux * this

@JvmName("biotTimesMaxwell")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>.times(flux: ScientificValue<PhysicalQuantity.MagneticFlux, Maxwell>) =
    flux * this

@JvmName("currentTimesFlux")
infix operator fun <FluxUnit : MagneticFlux, CurrentUnit : ElectricCurrent> ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>.times(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>
) = flux * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCurrent

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricInductance.times
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Abhenry
import com.splendo.kaluga.scientific.unit.Biot
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.ElectricInductance
import kotlin.jvm.JvmName

@JvmName("abampereTimesAbhenry")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>.times(inductance: ScientificValue<PhysicalQuantity.ElectricInductance, Abhenry>) =
    inductance * this

@JvmName("biotTimesAbhenry")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>.times(inductance: ScientificValue<PhysicalQuantity.ElectricInductance, Abhenry>) =
    inductance * this

@JvmName("currentTimesInductance")
infix operator fun <InductanceUnit : ElectricInductance, CurrentUnit : ElectricCurrent> ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>.times(
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>
) = inductance * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCurrent

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.voltage.times
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.Biot
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("abampereTimesAbvolt")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>.times(voltage: ScientificValue<PhysicalQuantity.Voltage, Abvolt>) =
    voltage * this

@JvmName("biotTimesAbvolt")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>.times(voltage: ScientificValue<PhysicalQuantity.Voltage, Abvolt>) =
    voltage * this

@JvmName("currentTimesVoltage")
infix operator fun <VoltageUnit : Voltage, ElectricCurrentUnit : ElectricCurrent> ScientificValue<PhysicalQuantity.ElectricCurrent, ElectricCurrentUnit>.times(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = voltage * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricCurrent

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.voltage.voltage
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Abohm
import com.splendo.kaluga.scientific.unit.Absiemens
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.Biot
import com.splendo.kaluga.scientific.unit.ElectricConductance
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Volt
import kotlin.jvm.JvmName

@JvmName("abampereTimesAbohm")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>.times(resistance: ScientificValue<PhysicalQuantity.ElectricResistance, Abohm>) =
    Abvolt.voltage(this, resistance)

@JvmName("biotTimesAbohm")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>.times(resistance: ScientificValue<PhysicalQuantity.ElectricResistance, Abohm>) =
    Abvolt.voltage(this, resistance)

@JvmName("currentTimesResistance")
infix operator fun <CurrentUnit : ElectricCurrent, ResistanceUnit : ElectricResistance> ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>.times(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
) = Volt.voltage(this, resistance)

@JvmName("abampereDivAbsiemens")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>.div(conductance: ScientificValue<PhysicalQuantity.ElectricConductance, Absiemens>) =
    Abvolt.voltage(this, conductance)

@JvmName("biotDivAbsiemens")
infix operator fun ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>.div(conductance: ScientificValue<PhysicalQuantity.ElectricConductance, Absiemens>) =
    Abvolt.voltage(this, conductance)

@JvmName("currentDivConductance")
infix operator fun <CurrentUnit : ElectricCurrent, ConductanceUnit : ElectricConductance> ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>.div(
    conductance: ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>
) = Volt.voltage(this, conductance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricInductance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricInductance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Frequency
import kotlin.jvm.JvmName

@JvmName("inductanceFromResistanceAndFrequencyDefault")
fun <
    ResistanceUnit : ElectricResistance,
    FrequencyUnit : Frequency,
    InductanceUnit : ElectricInductance
    > InductanceUnit.inductance(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>,
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
) = inductance(resistance, frequency, ::DefaultScientificValue)

@JvmName("inductanceFromResistanceAndFrequency")
fun <
    ResistanceUnit : ElectricResistance,
    FrequencyUnit : Frequency,
    InductanceUnit : ElectricInductance,
    Value : ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>
    > InductanceUnit.inductance(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>,
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>,
    factory: (Decimal, InductanceUnit) -> Value
) = byDividing(resistance, frequency, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricInductance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.ElectricInductance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("inductanceFromResistanceAndTimeDefault")
fun <
    ResistanceUnit : ElectricResistance,
    TimeUnit : Time,
    InductanceUnit : ElectricInductance
    > InductanceUnit.inductance(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = inductance(resistance, time, ::DefaultScientificValue)

@JvmName("inductanceFromResistanceAndTime")
fun <
    ResistanceUnit : ElectricResistance,
    TimeUnit : Time,
    InductanceUnit : ElectricInductance,
    Value : ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>
    > InductanceUnit.inductance(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, InductanceUnit) -> Value
) = byMultiplying(resistance, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricInductance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.ElectricInductance
import com.splendo.kaluga.scientific.unit.MagneticFlux
import kotlin.jvm.JvmName

@JvmName("inductanceFromFluxAndCurrentDefault")
fun <
    FluxUnit : MagneticFlux,
    CurrentUnit : ElectricCurrent,
    InductanceUnit : ElectricInductance
    > InductanceUnit.inductance(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
) = inductance(flux, current, ::DefaultScientificValue)

@JvmName("inductanceFromFluxAndCurrent")
fun <
    FluxUnit : MagneticFlux,
    CurrentUnit : ElectricCurrent,
    InductanceUnit : ElectricInductance,
    Value : ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>
    > InductanceUnit.inductance(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>,
    factory: (Decimal, InductanceUnit) -> Value
) = byDividing(flux, current, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricInductance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricResistance.resistance
import com.splendo.kaluga.scientific.unit.Abhenry
import com.splendo.kaluga.scientific.unit.Abohm
import com.splendo.kaluga.scientific.unit.ElectricInductance
import com.splendo.kaluga.scientific.unit.Frequency
import com.splendo.kaluga.scientific.unit.Ohm
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("abhenryTimesFrequency")
infix operator fun <FrequencyUnit : Frequency> ScientificValue<PhysicalQuantity.ElectricInductance, Abhenry>.times(
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
) = Abohm.resistance(this, frequency)

@JvmName("inductanceTimesFrequency")
infix operator fun <InductanceUnit : ElectricInductance, FrequencyUnit : Frequency> ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>.times(
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
) = Ohm.resistance(this, frequency)

@JvmName("abhenryDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.ElectricInductance, Abhenry>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Abohm.resistance(this, time)

@JvmName("inductanceDivTime")
infix operator fun <InductanceUnit : ElectricInductance, TimeUnit : Time> ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Ohm.resistance(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricInductance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.magneticFlux.flux
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Abhenry
import com.splendo.kaluga.scientific.unit.Biot
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.ElectricInductance
import com.splendo.kaluga.scientific.unit.Maxwell
import com.splendo.kaluga.scientific.unit.Weber
import kotlin.jvm.JvmName

@JvmName("abhenryTimesAbampere")
infix operator fun ScientificValue<PhysicalQuantity.ElectricInductance, Abhenry>.times(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>) =
    Maxwell.flux(this, current)

@JvmName("abhenryTimesBiot")
infix operator fun ScientificValue<PhysicalQuantity.ElectricInductance, Abhenry>.times(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>) =
    Maxwell.flux(this, current)

@JvmName("inductanceTimesCurrent")
infix operator fun <InductanceUnit : ElectricInductance, CurrentUnit : ElectricCurrent> ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>.times(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
) = Weber.flux(this, current)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricInductance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.ElectricInductance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Second
import kotlin.jvm.JvmName

@JvmName("inductanceDivResistance")
infix operator fun <InductanceUnit : ElectricInductance, ResistanceUnit : ElectricResistance> ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>.div(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
) = Second.time(this, resistance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricResistance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byInverting
import com.splendo.kaluga.scientific.unit.ElectricConductance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import kotlin.jvm.JvmName

@JvmName("electricResistanceFromInvertedConductanceDefault")
fun <
    ResistanceUnit : ElectricResistance,
    ConductanceUnit : ElectricConductance
    > ResistanceUnit.resistance(
    conductance: ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>,
) = resistance(conductance, ::DefaultScientificValue)

@JvmName("electricResistanceFromInvertedConductance")
fun <
    ResistanceUnit : ElectricResistance,
    ConductanceUnit : ElectricConductance,
    Value : ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
    > ResistanceUnit.resistance(
    conductance: ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>,
    factory: (Decimal, ResistanceUnit) -> Value
) = byInverting(conductance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricResistance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.ElectricInductance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Frequency
import kotlin.jvm.JvmName

@JvmName("resistanceFromInductanceAndFrequencyDefault")
fun <
    ResistanceUnit : ElectricResistance,
    FrequencyUnit : Frequency,
    InductanceUnit : ElectricInductance
    > ResistanceUnit.resistance(
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>,
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
) = resistance(inductance, frequency, ::DefaultScientificValue)

@JvmName("resistanceFromInductanceAndFrequency")
fun <
    ResistanceUnit : ElectricResistance,
    FrequencyUnit : Frequency,
    InductanceUnit : ElectricInductance,
    Value : ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
    > ResistanceUnit.resistance(
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>,
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>,
    factory: (Decimal, ResistanceUnit) -> Value
) = byMultiplying(inductance, frequency, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricResistance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricInductance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("resistanceFromInductanceAndTimeDefault")
fun <
    ResistanceUnit : ElectricResistance,
    TimeUnit : Time,
    InductanceUnit : ElectricInductance
    > ResistanceUnit.resistance(
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = resistance(inductance, time, ::DefaultScientificValue)

@JvmName("resistanceFromInductanceAndTime")
fun <
    ResistanceUnit : ElectricResistance,
    TimeUnit : Time,
    InductanceUnit : ElectricInductance,
    Value : ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
    > ResistanceUnit.resistance(
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, ResistanceUnit) -> Value
) = byDividing(inductance, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricResistance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.MagneticFlux
import kotlin.jvm.JvmName

@JvmName("resistanceFromFluxAndChargeDefault")
fun <
    ResistanceUnit : ElectricResistance,
    ChargeUnit : ElectricCharge,
    FluxUnit : MagneticFlux
    > ResistanceUnit.resistance(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>
) = resistance(flux, charge, ::DefaultScientificValue)

@JvmName("resistanceFromFluxAndCharge")
fun <
    ResistanceUnit : ElectricResistance,
    ChargeUnit : ElectricCharge,
    FluxUnit : MagneticFlux,
    Value : ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
    > ResistanceUnit.resistance(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>,
    factory: (Decimal, ResistanceUnit) -> Value
) = byDividing(flux, charge, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricResistance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("resistanceFromTimeAndCapacitanceDefault")
fun <
    CapacitanceUnit : ElectricCapacitance,
    TimeUnit : Time,
    ResistanceUnit : ElectricResistance
    > ResistanceUnit.resistance(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>
) = resistance(time, capacitance, ::DefaultScientificValue)

@JvmName("resistanceFromTimeAndCapacitance")
fun <
    CapacitanceUnit : ElectricCapacitance,
    TimeUnit : Time,
    ResistanceUnit : ElectricResistance,
    Value : ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
    > ResistanceUnit.resistance(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>,
    factory: (Decimal, ResistanceUnit) -> Value
) = byDividing(time, capacitance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricResistance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("resistanceFromVoltageAndCurrentDefault")
fun <
    CurrentUnit : ElectricCurrent,
    VoltageUnit : Voltage,
    ResistanceUnit : ElectricResistance
    > ResistanceUnit.resistance(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
) = resistance(voltage, current, ::DefaultScientificValue)

@JvmName("resistanceFromVoltageAndCurrent")
fun <
    CurrentUnit : ElectricCurrent,
    VoltageUnit : Voltage,
    ResistanceUnit : ElectricResistance,
    Value : ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
    > ResistanceUnit.resistance(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>,
    factory: (Decimal, ResistanceUnit) -> Value
) = byDividing(voltage, current, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricResistance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricConductance.conductance
import com.splendo.kaluga.scientific.unit.Abohm
import com.splendo.kaluga.scientific.unit.Absiemens
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Siemens
import kotlin.jvm.JvmName

@JvmName("abOhmConductance")
fun ScientificValue<PhysicalQuantity.ElectricResistance, Abohm>.conductance() =
    Absiemens.conductance(this)

@JvmName("conductance")
fun <ResistanceUnit : ElectricResistance> ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>.conductance() =
    Siemens.conductance(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricResistance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricInductance.inductance
import com.splendo.kaluga.scientific.unit.Abhenry
import com.splendo.kaluga.scientific.unit.Abohm
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Frequency
import com.splendo.kaluga.scientific.unit.Henry
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("abohmDivFrequency")
infix operator fun <FrequencyUnit : Frequency> ScientificValue<PhysicalQuantity.ElectricResistance, Abohm>.div(
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
) = Abhenry.inductance(this, frequency)

@JvmName("resistanceDivFrequency")
infix operator fun <ResistanceUnit : ElectricResistance, FrequencyUnit : Frequency> ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>.div(
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
) = Henry.inductance(this, frequency)

@JvmName("abohmTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.ElectricResistance, Abohm>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Abhenry.inductance(this, time)

@JvmName("resistanceTimesTime")
infix operator fun <ResistanceUnit : ElectricResistance, TimeUnit : Time> ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Henry.inductance(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricResistance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.frequency.frequency
import com.splendo.kaluga.scientific.unit.ElectricInductance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Hertz
import kotlin.jvm.JvmName

@JvmName("resistanceDivInductance")
infix operator fun <ResistanceUnit : ElectricResistance, InductanceUnit : ElectricInductance> ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>.div(
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>
) = Hertz.frequency(this, inductance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricResistance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.magneticFlux.flux
import com.splendo.kaluga.scientific.unit.Abcoulomb
import com.splendo.kaluga.scientific.unit.Abohm
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Maxwell
import com.splendo.kaluga.scientific.unit.Weber
import kotlin.jvm.JvmName

@JvmName("abohmTimesAbcoulomb")
infix operator fun ScientificValue<PhysicalQuantity.ElectricResistance, Abohm>.times(charge: ScientificValue<PhysicalQuantity.ElectricCharge, Abcoulomb>) =
    Maxwell.flux(this, charge)

@JvmName("resistanceTimesCharge")
infix operator fun <ResistanceUnit : ElectricResistance, ChargeUnit : ElectricCharge> ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>.times(
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>
) = Weber.flux(this, charge)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricResistance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCapacitance.times
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import kotlin.jvm.JvmName

@JvmName("resistanceTimesCapacitance")
infix operator fun <CapacitanceUnit : ElectricCapacitance, ResistanceUnit : ElectricResistance> ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>.times(
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>
) = capacitance * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.electricResistance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCurrent.times
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Abohm
import com.splendo.kaluga.scientific.unit.Biot
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.ElectricResistance
import kotlin.jvm.JvmName

@JvmName("abohmTimesAbampere")
infix operator fun ScientificValue<PhysicalQuantity.ElectricResistance, Abohm>.times(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>) =
    current * this

@JvmName("abohmTimesBiot")
infix operator fun ScientificValue<PhysicalQuantity.ElectricResistance, Abohm>.times(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>) =
    current * this

@JvmName("resistanceTimesCurrent")
infix operator fun <CurrentUnit : ElectricCurrent, ResistanceUnit : ElectricResistance> ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>.times(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
) = current * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Action
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("energyFromActionAndTimeDefault")
fun <
    ActionUnit : Action,
    TimeUnit : Time,
    EnergyUnit : Energy
    > EnergyUnit.energy(
    action: ScientificValue<PhysicalQuantity.Action, ActionUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = energy(action, time, ::DefaultScientificValue)

@JvmName("energyFromActionAndTime")
fun <
    ActionUnit : Action,
    TimeUnit : Time,
    EnergyUnit : Energy,
    Value : ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
    > EnergyUnit.energy(
    action: ScientificValue<PhysicalQuantity.Action, ActionUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, EnergyUnit) -> Value
) = byDividing(action, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("energyFromChargeAndVoltageDefault")
fun <
    EnergyUnit : Energy,
    ChargeUnit : ElectricCharge,
    VoltageUnit : Voltage
    > EnergyUnit.energy(
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = energy(charge, voltage, ::DefaultScientificValue)

@JvmName("energyFromChargeAndVoltage")
fun <
    EnergyUnit : Energy,
    ChargeUnit : ElectricCharge,
    VoltageUnit : Voltage,
    Value : ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
    > EnergyUnit.energy(
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    factory: (Decimal, EnergyUnit) -> Value
) = byMultiplying(charge, voltage, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Length
import kotlin.jvm.JvmName

@JvmName("energyFromForceAndDistanceDefault")
fun <
    EnergyUnit : Energy,
    ForceUnit : Force,
    LengthUnit : Length
    > EnergyUnit.energy(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = energy(force, distance, ::DefaultScientificValue)

@JvmName("energyFromForceAndDistance")
fun <
    EnergyUnit : Energy,
    ForceUnit : Force,
    LengthUnit : Length,
    Value : ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
    > EnergyUnit.energy(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    factory: (Decimal, EnergyUnit) -> Value
) = byMultiplying(force, distance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.converter.temperature.deltaValue
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.HeatCapacity
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.Temperature
import kotlin.jvm.JvmName

@JvmName("energyFromHeatCapacityAndTemperatureDefault")
fun <
    EnergyUnit : Energy,
    TemperatureUnit : Temperature,
    HeatCapacityUnit : HeatCapacity
    > EnergyUnit.energy(
    heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, HeatCapacityUnit>,
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = energy(heatCapacity, temperature, ::DefaultScientificValue)

@JvmName("energyFromHeatCapacityAndTemperature")
fun <
    EnergyUnit : Energy,
    TemperatureUnit : Temperature,
    HeatCapacityUnit : HeatCapacity,
    Value : ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
    > EnergyUnit.energy(
    heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, HeatCapacityUnit>,
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>,
    factory: (Decimal, EnergyUnit) -> Value
) = byMultiplying(heatCapacity, Kelvin.deltaValue(temperature), factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.IonizingRadiationAbsorbedDose
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("energyFromAbsorbedDoseAndWeightDefault")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    AbsorbedDoseUnit : IonizingRadiationAbsorbedDose
    > EnergyUnit.energy(
    absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = energy(absorbedDose, weight, ::DefaultScientificValue)

@JvmName("energyFromAbsorbedDoseAndWeight")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    AbsorbedDoseUnit : IonizingRadiationAbsorbedDose,
    Value : ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
    > EnergyUnit.energy(
    absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    factory: (Decimal, EnergyUnit) -> Value
) = byMultiplying(absorbedDose, weight, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.IonizingRadiationEquivalentDose
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("energyFromEquivalentDoseAndWeightDefault")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    EquivalentDoseUnit : IonizingRadiationEquivalentDose
    > EnergyUnit.energy(
    equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = energy(equivalentDose, weight, ::DefaultScientificValue)

@JvmName("energyFromEquivalentDoseAndWeight")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    EquivalentDoseUnit : IonizingRadiationEquivalentDose,
    Value : ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
    > EnergyUnit.energy(
    equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    factory: (Decimal, EnergyUnit) -> Value
) = byMultiplying(equivalentDose, weight, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.MagneticFlux
import kotlin.jvm.JvmName

@JvmName("energyFromFluxAndCurrentDefault")
fun <
    EnergyUnit : Energy,
    CurrentUnit : ElectricCurrent,
    FluxUnit : MagneticFlux
    > EnergyUnit.energy(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
) = energy(flux, current, ::DefaultScientificValue)

@JvmName("energyFromFluxAndCurrent")
fun <
    EnergyUnit : Energy,
    CurrentUnit : ElectricCurrent,
    FluxUnit : MagneticFlux,
    Value : ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
    > EnergyUnit.energy(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>,
    factory: (Decimal, EnergyUnit) -> Value
) = byMultiplying(flux, current, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.MolarEnergy
import kotlin.jvm.JvmName

@JvmName("energyFromMolarEnergyAndAmountOfSubstanceDefault")
fun <
    EnergyUnit : Energy,
    AmountOfSubstanceUnit : AmountOfSubstance,
    MolarEnergyUnit : MolarEnergy
    > EnergyUnit.energy(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>,
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = energy(molarEnergy, amountOfSubstance, ::DefaultScientificValue)

@JvmName("energyFromMolarEnergyAndAmountOfSubstance")
fun <
    EnergyUnit : Energy,
    AmountOfSubstanceUnit : AmountOfSubstance,
    MolarEnergyUnit : MolarEnergy,
    Value : ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
    > EnergyUnit.energy(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>,
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    factory: (Decimal, EnergyUnit) -> Value
) = byMultiplying(molarEnergy, amountOfSubstance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("energyFromPowerAndTimeDefault")
fun <
    EnergyUnit : Energy,
    TimeUnit : Time,
    PowerUnit : Power
    > EnergyUnit.energy(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = energy(power, time, ::DefaultScientificValue)

@JvmName("energyFromPowerAndTime")
fun <
    EnergyUnit : Energy,
    TimeUnit : Time,
    PowerUnit : Power,
    Value : ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
    > EnergyUnit.energy(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, EnergyUnit) -> Value
) = byMultiplying(power, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Pressure
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("energyFromPressureAndVolumeDefault")
fun <
    EnergyUnit : Energy,
    PressureUnit : Pressure,
    VolumeUnit : Volume
    > EnergyUnit.energy(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>,
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = energy(pressure, volume, ::DefaultScientificValue)

@JvmName("energyFromPressureAndVolume")
fun <
    EnergyUnit : Energy,
    PressureUnit : Pressure,
    VolumeUnit : Volume,
    Value : ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
    > EnergyUnit.energy(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>,
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    factory: (Decimal, EnergyUnit) -> Value
) = byMultiplying(pressure, volume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("energyFromSpecificEnergyAndWeightDefault")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    SpecificEnergyUnit : SpecificEnergy
    > EnergyUnit.energy(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = energy(specificEnergy, weight, ::DefaultScientificValue)

@JvmName("energyFromSpecificEnergyAndWeight")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    SpecificEnergyUnit : SpecificEnergy,
    Value : ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
    > EnergyUnit.energy(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    factory: (Decimal, EnergyUnit) -> Value
) = byMultiplying(specificEnergy, weight, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.SurfaceTension
import kotlin.jvm.JvmName

@JvmName("energyFromSurfaceTensionAndAreaDefault")
fun <
    EnergyUnit : Energy,
    AreaUnit : Area,
    SurfaceTensionUnit : SurfaceTension
    > EnergyUnit.energy(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = energy(surfaceTension, area, ::DefaultScientificValue)

@JvmName("energyFromSurfaceTensionAndArea")
fun <
    EnergyUnit : Energy,
    AreaUnit : Area,
    SurfaceTensionUnit : SurfaceTension,
    Value : ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
    > EnergyUnit.energy(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, EnergyUnit) -> Value
) = byMultiplying(surfaceTension, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.action.action
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.ImperialEnergy
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.MetricAndImperialEnergy
import com.splendo.kaluga.scientific.unit.MetricEnergy
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.x
import kotlin.jvm.JvmName

@JvmName("metricAndImperialEnergyTimesTime")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit x time.unit).action(this, time)

@JvmName("metricEnergyTimesTime")
infix operator fun <EnergyUnit : MetricEnergy, TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit x time.unit).action(this, time)

@JvmName("imperialEnergyTimesTime")
infix operator fun <EnergyUnit : ImperialEnergy, TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit x time.unit).action(this, time)

@JvmName("energyTimesTime")
infix operator fun <EnergyUnit : Energy, TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (Joule x time.unit).action(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.amountOfSubstance.amountOfSubstance
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.MolarEnergy
import kotlin.jvm.JvmName

@JvmName("energyDivMolarEnergy")
infix operator fun <EnergyUnit : Energy, MolarEnergyUnit : MolarEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>
) = molarEnergy.unit.per.amountOfSubstance(this, molarEnergy)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.area
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.ImperialEnergy
import com.splendo.kaluga.scientific.unit.ImperialSurfaceTension
import com.splendo.kaluga.scientific.unit.InchOunceForce
import com.splendo.kaluga.scientific.unit.InchPoundForce
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricAndImperialEnergy
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.MetricSurfaceTension
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.SquareFoot
import com.splendo.kaluga.scientific.unit.SquareInch
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.SurfaceTension
import com.splendo.kaluga.scientific.unit.UKImperialSurfaceTension
import com.splendo.kaluga.scientific.unit.USCustomarySurfaceTension
import kotlin.jvm.JvmName

@JvmName("ergDivMetricSurfaceTension")
infix operator fun ScientificValue<PhysicalQuantity.Energy, Erg>.div(surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, MetricSurfaceTension>) =
    SquareCentimeter.area(this, surfaceTension)

@JvmName("ergMultipleDivMetricSurfaceTension")
infix operator fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, MetricSurfaceTension>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    SquareCentimeter.area(this, surfaceTension)

@JvmName("metricAndImperialEnergyDivMetricSurfaceTension")
infix operator fun <EnergyUnit : MetricAndImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, MetricSurfaceTension>
) = SquareMeter.area(this, surfaceTension)

@JvmName("inchPoundForceDivImperialSurfaceTension")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchPoundForce>.div(surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, ImperialSurfaceTension>) =
    SquareInch.area(this, surfaceTension)

@JvmName("inchPoundForceDivUKImperialSurfaceTension")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchPoundForce>.div(surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, UKImperialSurfaceTension>) =
    SquareInch.area(this, surfaceTension)

@JvmName("inchPoundForceDivUSCustomarySurfaceTension")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchPoundForce>.div(surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, USCustomarySurfaceTension>) =
    SquareInch.area(this, surfaceTension)

@JvmName("inchOunceForceDivImperialSurfaceTension")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchOunceForce>.div(surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, ImperialSurfaceTension>) =
    SquareInch.area(this, surfaceTension)

@JvmName("inchOunceForceDivUKImperialSurfaceTension")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchOunceForce>.div(surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, UKImperialSurfaceTension>) =
    SquareInch.area(this, surfaceTension)

@JvmName("inchOunceForceDivUSCustomarySurfaceTension")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchOunceForce>.div(surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, USCustomarySurfaceTension>) =
    SquareInch.area(this, surfaceTension)

@JvmName("metricAndImperialEnergyDivImperialSurfaceTension")
infix operator fun <EnergyUnit : MetricAndImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, ImperialSurfaceTension>
) = SquareFoot.area(this, surfaceTension)

@JvmName("metricAndImperialEnergyDivUKImperialSurfaceTension")
infix operator fun <EnergyUnit : MetricAndImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, UKImperialSurfaceTension>
) = SquareFoot.area(this, surfaceTension)

@JvmName("metricAndImperialEnergyDivUSCustomarySurfaceTension")
infix operator fun <EnergyUnit : MetricAndImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, USCustomarySurfaceTension>
) = SquareFoot.area(this, surfaceTension)

@JvmName("imperialEnergyDivImperialSurfaceTension")
infix operator fun <EnergyUnit : ImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, ImperialSurfaceTension>
) = SquareFoot.area(this, surfaceTension)

@JvmName("imperialEnergyDivUKImperialSurfaceTension")
infix operator fun <EnergyUnit : ImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, UKImperialSurfaceTension>
) = SquareFoot.area(this, surfaceTension)

@JvmName("imperialEnergyDivUSCustomarySurfaceTension")
infix operator fun <EnergyUnit : ImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, USCustomarySurfaceTension>
) = SquareFoot.area(this, surfaceTension)

@JvmName("energyDivSurfaceTension")
infix operator fun <EnergyUnit : Energy, SurfaceTensionUnit : SurfaceTension> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>
) = SquareMeter.area(this, surfaceTension)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCharge.charge
import com.splendo.kaluga.scientific.unit.Abcoulomb
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.Coulomb
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("ergDivAbvolt")
infix operator fun ScientificValue<PhysicalQuantity.Energy, Erg>.div(voltage: ScientificValue<PhysicalQuantity.Voltage, Abvolt>) =
    Abcoulomb.charge(this, voltage)

@JvmName("ergMultipleDivAbvolt")
infix operator fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(voltage: ScientificValue<PhysicalQuantity.Voltage, Abvolt>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    Abcoulomb.charge(this, voltage)

@JvmName("energyDivVoltage")
infix operator fun <EnergyUnit : Energy, VoltageUnit : Voltage> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = Coulomb.charge(this, voltage)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCurrent.current
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Ampere
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.Maxwell
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import kotlin.jvm.JvmName

@JvmName("ergDivMaxwell")
infix operator fun ScientificValue<PhysicalQuantity.Energy, Erg>.div(flux: ScientificValue<PhysicalQuantity.MagneticFlux, Maxwell>) =
    Abampere.current(this, flux)

@JvmName("ergMultipleDivMaxwell")
infix operator fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(flux: ScientificValue<PhysicalQuantity.MagneticFlux, Maxwell>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    Abampere.current(this, flux)

@JvmName("energyDivFlux")
infix operator fun <EnergyUnit : Energy, FluxUnit : MagneticFlux> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>
) = Ampere.current(this, flux)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.force.force
import com.splendo.kaluga.scientific.unit.Centimeter
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.FootPoundal
import com.splendo.kaluga.scientific.unit.ImperialEnergy
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.Inch
import com.splendo.kaluga.scientific.unit.InchOunceForce
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricAndImperialEnergy
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Newton
import com.splendo.kaluga.scientific.unit.OunceForce
import com.splendo.kaluga.scientific.unit.PoundForce
import com.splendo.kaluga.scientific.unit.Poundal
import kotlin.jvm.JvmName

@JvmName("ergDivCentimeter")
infix operator fun ScientificValue<PhysicalQuantity.Energy, Erg>.div(distance: ScientificValue<PhysicalQuantity.Length, Centimeter>) =
    Dyne.force(this, distance)

@JvmName("ergMultipleDivCentimeter")
infix operator fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(distance: ScientificValue<PhysicalQuantity.Length, Centimeter>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    Dyne.force(this, distance)

@JvmName("metricAndImperialEnergyDivMetricLength")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = Newton.force(this, distance)

@JvmName("footPoundalDivImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Energy, FootPoundal>.div(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = Poundal.force(this, distance)

@JvmName("inchOunceForceDivInch")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchOunceForce>.div(distance: ScientificValue<PhysicalQuantity.Length, Inch>) =
    OunceForce.force(this, distance)

@JvmName("metricAndImperialEnergyDivImperialLength")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = PoundForce.force(this, distance)

@JvmName("imperialEnergyDivImperialLength")
infix operator fun <EnergyUnit : ImperialEnergy, LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = PoundForce.force(this, distance)

@JvmName("energyDivLength")
infix operator fun <EnergyUnit : Energy, LengthUnit : Length> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = Newton.force(this, distance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.heatCapacity.heatCapacity
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.ImperialEnergy
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.MetricAndImperialEnergy
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialTemperature
import com.splendo.kaluga.scientific.unit.MetricEnergy
import com.splendo.kaluga.scientific.unit.Temperature
import com.splendo.kaluga.scientific.unit.USCustomaryTemperature
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricAndImperialEnergyDivMetricAndUKImperialTemperature")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (unit per temperature.unit).heatCapacity(this, temperature)

@JvmName("metricEnergyDivMetricAndUKImperialTemperature")
infix operator fun <EnergyUnit : MetricEnergy, TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (unit per temperature.unit).heatCapacity(this, temperature)

@JvmName("imperialEnergyDivMetricAndUKImperialTemperature")
infix operator fun <EnergyUnit : ImperialEnergy, TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (unit per temperature.unit).heatCapacity(this, temperature)

@JvmName("metricAndImperialEnergyDivUSCustomaryTemperature")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, TemperatureUnit : USCustomaryTemperature> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (unit per temperature.unit).heatCapacity(this, temperature)

@JvmName("imperialEnergyDivUSCustomaryTemperature")
infix operator fun <EnergyUnit : ImperialEnergy, TemperatureUnit : USCustomaryTemperature> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (unit per temperature.unit).heatCapacity(this, temperature)

@JvmName("energyDivTemperature")
infix operator fun <EnergyUnit : Energy, TemperatureUnit : Temperature> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (Joule per Kelvin).heatCapacity(this, temperature)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.ionizingRadiationAbsorbedDose.absorbedDose
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.Gram
import com.splendo.kaluga.scientific.unit.Gray
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Rad
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("ergAbsorbedByGram")
infix fun ScientificValue<PhysicalQuantity.Energy, Erg>.absorbedBy(gram: ScientificValue<PhysicalQuantity.Weight, Gram>) =
    Rad.absorbedDose(this, gram)

@JvmName("ergMultipleAbsorbedByGram")
infix fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.absorbedBy(gram: ScientificValue<PhysicalQuantity.Weight, Gram>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    Rad.absorbedDose(this, gram)

@JvmName("energyAbsorbedByWeight")
infix fun <EnergyUnit : Energy, WeightUnit : Weight> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.absorbedBy(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = Gray.absorbedDose(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.ionizingRadiationEquivalentDose.equivalentDose
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.Gram
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.RoentgenEquivalentMan
import com.splendo.kaluga.scientific.unit.Sievert
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("ergEquivalentDoseByGram")
infix fun ScientificValue<PhysicalQuantity.Energy, Erg>.equivalentDoseBy(gram: ScientificValue<PhysicalQuantity.Weight, Gram>) =
    RoentgenEquivalentMan.equivalentDose(this, gram)

@JvmName("ergMultipleEquivalentDoseByGram")
infix fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.equivalentDoseBy(gram: ScientificValue<PhysicalQuantity.Weight, Gram>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    RoentgenEquivalentMan.equivalentDose(this, gram)

@JvmName("energyEquivalentDoseByWeight")
infix fun <EnergyUnit : Energy, WeightUnit : Weight> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.equivalentDoseBy(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = Sievert.equivalentDose(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.length.distance
import com.splendo.kaluga.scientific.unit.Centimeter
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.Foot
import com.splendo.kaluga.scientific.unit.FootPoundForce
import com.splendo.kaluga.scientific.unit.FootPoundal
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.ImperialEnergy
import com.splendo.kaluga.scientific.unit.ImperialForce
import com.splendo.kaluga.scientific.unit.Inch
import com.splendo.kaluga.scientific.unit.InchOunceForce
import com.splendo.kaluga.scientific.unit.InchPoundForce
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricAndImperialEnergy
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.OunceForce
import com.splendo.kaluga.scientific.unit.PoundForce
import com.splendo.kaluga.scientific.unit.Poundal
import com.splendo.kaluga.scientific.unit.UKImperialForce
import com.splendo.kaluga.scientific.unit.USCustomaryForce
import kotlin.jvm.JvmName

@JvmName("ergDivDyne")
infix operator fun ScientificValue<PhysicalQuantity.Energy, Erg>.div(force: ScientificValue<PhysicalQuantity.Force, Dyne>) =
    Centimeter.distance(this, force)

@JvmName("ergMultipleDivDyne")
infix operator fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(force: ScientificValue<PhysicalQuantity.Force, Dyne>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    Centimeter.distance(this, force)

@JvmName("ergDivDyneMultiple")
infix operator fun <DyneUnit> ScientificValue<PhysicalQuantity.Energy, Erg>.div(force: ScientificValue<PhysicalQuantity.Force, DyneUnit>) where DyneUnit : Force, DyneUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> =
    Centimeter.distance(this, force)

@JvmName("ergMultipleDivDyneMultiple")
infix operator fun <ErgUnit, DyneUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(force: ScientificValue<PhysicalQuantity.Force, DyneUnit>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg>, DyneUnit : Force, DyneUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> =
    Centimeter.distance(this, force)

@JvmName("footPoundalDivPoundal")
infix operator fun ScientificValue<PhysicalQuantity.Energy, FootPoundal>.div(force: ScientificValue<PhysicalQuantity.Force, Poundal>) =
    Foot.distance(this, force)

@JvmName("footPoundForceDivPoundForce")
infix operator fun ScientificValue<PhysicalQuantity.Energy, FootPoundForce>.div(force: ScientificValue<PhysicalQuantity.Force, PoundForce>) =
    Foot.distance(this, force)

@JvmName("inchPoundForceDivPoundForce")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchPoundForce>.div(force: ScientificValue<PhysicalQuantity.Force, PoundForce>) =
    Inch.distance(this, force)

@JvmName("inchOunceForceDivOunceForce")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchOunceForce>.div(force: ScientificValue<PhysicalQuantity.Force, OunceForce>) =
    Inch.distance(this, force)

@JvmName("imperialEnergyDivImperialForce")
infix operator fun <EnergyUnit : ImperialEnergy, ForceUnit : ImperialForce> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = Foot.distance(this, force)

@JvmName("imperialEnergyDivUKImperialForce")
infix operator fun <EnergyUnit : ImperialEnergy, ForceUnit : UKImperialForce> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = Foot.distance(this, force)

@JvmName("imperialEnergyDivUSCustomaryForce")
infix operator fun <EnergyUnit : ImperialEnergy, ForceUnit : USCustomaryForce> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = Foot.distance(this, force)

@JvmName("metricAndImperialEnergyDivImperialForce")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, ForceUnit : ImperialForce> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = Foot.distance(this, force)

@JvmName("metricAndImperialEnergyDivUKImperialForce")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, ForceUnit : UKImperialForce> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = Foot.distance(this, force)

@JvmName("metricAndImperialEnergyDivUSCustomaryForce")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, ForceUnit : USCustomaryForce> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = Foot.distance(this, force)

@JvmName("energyDivForce")
infix operator fun <EnergyUnit : Energy, ForceUnit : Force> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = Meter.distance(this, force)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.magneticFlux.flux
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Biot
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.Maxwell
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Weber
import kotlin.jvm.JvmName

@JvmName("ergDivAbampere")
infix operator fun ScientificValue<PhysicalQuantity.Energy, Erg>.div(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>) =
    Maxwell.flux(this, current)

@JvmName("ergMultipleDivAbampere")
infix operator fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    Maxwell.flux(this, current)

@JvmName("ergDivBiot")
infix operator fun ScientificValue<PhysicalQuantity.Energy, Erg>.div(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>) =
    Maxwell.flux(this, current)

@JvmName("ergMultipleDivBiot")
infix operator fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    Maxwell.flux(this, current)

@JvmName("energyDivCurrent")
infix operator fun <EnergyUnit : Energy, CurrentUnit : ElectricCurrent> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
) = Weber.flux(this, current)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarEnergy.molarEnergy
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.ImperialEnergy
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.MetricAndImperialEnergy
import com.splendo.kaluga.scientific.unit.MetricEnergy
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricAndImperialEnergyDivAmountOfSubstance")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (unit per amountOfSubstance.unit).molarEnergy(this, amountOfSubstance)

@JvmName("metricEnergyDivAmountOfSubstance")
infix operator fun <EnergyUnit : MetricEnergy, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (unit per amountOfSubstance.unit).molarEnergy(this, amountOfSubstance)

@JvmName("imperialEnergyDivAmountOfSubstance")
infix operator fun <EnergyUnit : ImperialEnergy, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (unit per amountOfSubstance.unit).molarEnergy(this, amountOfSubstance)

@JvmName("energyDivAmountOfSubstance")
infix operator fun <EnergyUnit : Energy, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (Joule per amountOfSubstance.unit).molarEnergy(this, amountOfSubstance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.power.power
import com.splendo.kaluga.scientific.unit.BritishThermalUnit
import com.splendo.kaluga.scientific.unit.BritishThermalUnitPerHour
import com.splendo.kaluga.scientific.unit.BritishThermalUnitPerMinute
import com.splendo.kaluga.scientific.unit.BritishThermalUnitPerSecond
import com.splendo.kaluga.scientific.unit.Centijoule
import com.splendo.kaluga.scientific.unit.Centiwatt
import com.splendo.kaluga.scientific.unit.CentiwattHour
import com.splendo.kaluga.scientific.unit.Decajoule
import com.splendo.kaluga.scientific.unit.Decawatt
import com.splendo.kaluga.scientific.unit.DecawattHour
import com.splendo.kaluga.scientific.unit.Decijoule
import com.splendo.kaluga.scientific.unit.Deciwatt
import com.splendo.kaluga.scientific.unit.DeciwattHour
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.ErgPerSecond
import com.splendo.kaluga.scientific.unit.FootPoundForce
import com.splendo.kaluga.scientific.unit.FootPoundForcePerMinute
import com.splendo.kaluga.scientific.unit.FootPoundForcePerSecond
import com.splendo.kaluga.scientific.unit.FootPoundal
import com.splendo.kaluga.scientific.unit.Gigajoule
import com.splendo.kaluga.scientific.unit.Gigawatt
import com.splendo.kaluga.scientific.unit.GigawattHour
import com.splendo.kaluga.scientific.unit.Hectojoule
import com.splendo.kaluga.scientific.unit.Hectowatt
import com.splendo.kaluga.scientific.unit.HectowattHour
import com.splendo.kaluga.scientific.unit.Horsepower
import com.splendo.kaluga.scientific.unit.HorsepowerHour
import com.splendo.kaluga.scientific.unit.Hour
import com.splendo.kaluga.scientific.unit.ImperialEnergy
import com.splendo.kaluga.scientific.unit.InchOunceForce
import com.splendo.kaluga.scientific.unit.InchPoundForce
import com.splendo.kaluga.scientific.unit.InchPoundForcePerMinute
import com.splendo.kaluga.scientific.unit.InchPoundForcePerSecond
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Kilojoule
import com.splendo.kaluga.scientific.unit.Kilowatt
import com.splendo.kaluga.scientific.unit.KilowattHour
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.Megajoule
import com.splendo.kaluga.scientific.unit.Megawatt
import com.splendo.kaluga.scientific.unit.MegawattHour
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Microjoule
import com.splendo.kaluga.scientific.unit.Microwatt
import com.splendo.kaluga.scientific.unit.MicrowattHour
import com.splendo.kaluga.scientific.unit.Millijoule
import com.splendo.kaluga.scientific.unit.Milliwatt
import com.splendo.kaluga.scientific.unit.MilliwattHour
import com.splendo.kaluga.scientific.unit.Minute
import com.splendo.kaluga.scientific.unit.Nanojoule
import com.splendo.kaluga.scientific.unit.Nanowatt
import com.splendo.kaluga.scientific.unit.NanowattHour
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Watt
import com.splendo.kaluga.scientific.unit.WattHour
import com.splendo.kaluga.scientific.unit.imperial
import com.splendo.kaluga.scientific.unit.metric
import kotlin.jvm.JvmName

@JvmName("wattHourDivHour")
infix operator fun ScientificValue<PhysicalQuantity.Energy, WattHour>.div(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    Watt.power(this, time)

@JvmName("nanowattHourDivHour")
infix operator fun ScientificValue<PhysicalQuantity.Energy, NanowattHour>.div(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    Nanowatt.power(this, time)

@JvmName("microwattHourDivHour")
infix operator fun ScientificValue<PhysicalQuantity.Energy, MicrowattHour>.div(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    Microwatt.power(this, time)

@JvmName("milliwattHourDivHour")
infix operator fun ScientificValue<PhysicalQuantity.Energy, MilliwattHour>.div(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    Milliwatt.power(this, time)

@JvmName("centiwattHourDivHour")
infix operator fun ScientificValue<PhysicalQuantity.Energy, CentiwattHour>.div(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    Centiwatt.power(this, time)

@JvmName("deciwattHourDivHour")
infix operator fun ScientificValue<PhysicalQuantity.Energy, DeciwattHour>.div(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    Deciwatt.power(this, time)

@JvmName("decawattHourDivHour")
infix operator fun ScientificValue<PhysicalQuantity.Energy, DecawattHour>.div(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    Decawatt.power(this, time)

@JvmName("hectowattHourDivHour")
infix operator fun ScientificValue<PhysicalQuantity.Energy, HectowattHour>.div(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    Hectowatt.power(this, time)

@JvmName("kilowattHourDivHour")
infix operator fun ScientificValue<PhysicalQuantity.Energy, KilowattHour>.div(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    Kilowatt.power(this, time)

@JvmName("megawattHourDivHour")
infix operator fun ScientificValue<PhysicalQuantity.Energy, MegawattHour>.div(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    Megawatt.power(this, time)

@JvmName("gigawattHourDivHour")
infix operator fun ScientificValue<PhysicalQuantity.Energy, GigawattHour>.div(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    Gigawatt.power(this, time)

@JvmName("jouleDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, Joule>.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    Watt.metric.power(this, time)

@JvmName("nanojouleDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, Nanojoule>.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    Nanowatt.metric.power(this, time)

@JvmName("microjouleDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, Microjoule>.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    Microwatt.metric.power(this, time)

@JvmName("millijouleDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, Millijoule>.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    Milliwatt.metric.power(this, time)

@JvmName("centijouleDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, Centijoule>.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    Centiwatt.metric.power(this, time)

@JvmName("decijouleDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, Decijoule>.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    Deciwatt.metric.power(this, time)

@JvmName("decajouleDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, Decajoule>.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    Decawatt.metric.power(this, time)

@JvmName("hectojouleDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, Hectojoule>.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    Hectowatt.metric.power(this, time)

@JvmName("kilojouleDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, Kilojoule>.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    Kilowatt.metric.power(this, time)

@JvmName("megajouleDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, Megajoule>.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    Megawatt.metric.power(this, time)

@JvmName("gigajouleDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, Gigajoule>.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    Gigawatt.metric.power(this, time)

@JvmName("ergDivSecond")
infix operator fun ScientificValue<PhysicalQuantity.Energy, Erg>.div(time: ScientificValue<PhysicalQuantity.Time, Second>) =
    ErgPerSecond.power(this, time)

@JvmName("ergMultipleDivSecond")
infix operator fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(time: ScientificValue<PhysicalQuantity.Time, Second>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    ErgPerSecond.power(this, time)

@JvmName("footPoundalDivSecond")
infix operator fun ScientificValue<PhysicalQuantity.Energy, FootPoundal>.div(time: ScientificValue<PhysicalQuantity.Time, Second>) =
    FootPoundForcePerSecond.power(this, time)

@JvmName("footPoundalDivMinute")
infix operator fun ScientificValue<PhysicalQuantity.Energy, FootPoundal>.div(time: ScientificValue<PhysicalQuantity.Time, Minute>) =
    FootPoundForcePerMinute.power(this, time)

@JvmName("footPoundForceDivSecond")
infix operator fun ScientificValue<PhysicalQuantity.Energy, FootPoundForce>.div(time: ScientificValue<PhysicalQuantity.Time, Second>) =
    FootPoundForcePerSecond.power(this, time)

@JvmName("footPoundForceDivMinute")
infix operator fun ScientificValue<PhysicalQuantity.Energy, FootPoundForce>.div(time: ScientificValue<PhysicalQuantity.Time, Minute>) =
    FootPoundForcePerMinute.power(this, time)

@JvmName("inchPoundForceDivSecond")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchPoundForce>.div(time: ScientificValue<PhysicalQuantity.Time, Second>) =
    InchPoundForcePerSecond.power(this, time)

@JvmName("inchPoundForceDivMinute")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchPoundForce>.div(time: ScientificValue<PhysicalQuantity.Time, Minute>) =
    InchPoundForcePerMinute.power(this, time)

@JvmName("inchOunceForceDivSecond")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchOunceForce>.div(time: ScientificValue<PhysicalQuantity.Time, Second>) =
    InchPoundForcePerSecond.power(this, time)

@JvmName("inchOunceForceDivMinute")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchOunceForce>.div(time: ScientificValue<PhysicalQuantity.Time, Minute>) =
    InchPoundForcePerMinute.power(this, time)

@JvmName("horsepowerHourDivHour")
infix operator fun ScientificValue<PhysicalQuantity.Energy, HorsepowerHour>.div(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    Horsepower.power(this, time)

@JvmName("britishThermalUnitDivHour")
infix operator fun ScientificValue<PhysicalQuantity.Energy, BritishThermalUnit>.div(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    BritishThermalUnitPerHour.power(this, time)

@JvmName("britishThermalUnitDivMinute")
infix operator fun ScientificValue<PhysicalQuantity.Energy, BritishThermalUnit>.div(time: ScientificValue<PhysicalQuantity.Time, Minute>) =
    BritishThermalUnitPerMinute.power(this, time)

@JvmName("britishThermalUnitDivSecond")
infix operator fun ScientificValue<PhysicalQuantity.Energy, BritishThermalUnit>.div(time: ScientificValue<PhysicalQuantity.Time, Second>) =
    BritishThermalUnitPerSecond.power(this, time)

@JvmName("imperialEnergyDivTime")
infix operator fun <EnergyUnit : ImperialEnergy, TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Watt.imperial.power(this, time)

@JvmName("energyDivTime")
infix operator fun <EnergyUnit : Energy, TimeUnit : Time> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Watt.power(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.pressure.pressure
import com.splendo.kaluga.scientific.unit.Barye
import com.splendo.kaluga.scientific.unit.CubicCentimeter
import com.splendo.kaluga.scientific.unit.CubicFoot
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.FootPoundForce
import com.splendo.kaluga.scientific.unit.FootPoundal
import com.splendo.kaluga.scientific.unit.ImperialEnergy
import com.splendo.kaluga.scientific.unit.ImperialVolume
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricAndImperialEnergy
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Pascal
import com.splendo.kaluga.scientific.unit.PoundSquareFoot
import com.splendo.kaluga.scientific.unit.PoundSquareInch
import com.splendo.kaluga.scientific.unit.UKImperialVolume
import com.splendo.kaluga.scientific.unit.USCustomaryVolume
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.ukImperial
import com.splendo.kaluga.scientific.unit.usCustomary
import kotlin.jvm.JvmName

@JvmName("ergDivCubicCentimeter")
infix operator fun ScientificValue<PhysicalQuantity.Energy, Erg>.div(volume: ScientificValue<PhysicalQuantity.Volume, CubicCentimeter>) =
    Barye.pressure(this, volume)

@JvmName("ergMultipleDivCubicCentimeter")
infix operator fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(volume: ScientificValue<PhysicalQuantity.Volume, CubicCentimeter>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    Barye.pressure(this, volume)

@JvmName("footPoundalDivCubicFoot")
infix operator fun ScientificValue<PhysicalQuantity.Energy, FootPoundal>.div(volume: ScientificValue<PhysicalQuantity.Volume, CubicFoot>) =
    PoundSquareFoot.pressure(this, volume)

@JvmName("footPoundForceDivCubicFoot")
infix operator fun ScientificValue<PhysicalQuantity.Energy, FootPoundForce>.div(volume: ScientificValue<PhysicalQuantity.Volume, CubicFoot>) =
    PoundSquareFoot.pressure(this, volume)

@JvmName("imperialEnergyDivImperialVolume")
infix operator fun <EnergyUnit : ImperialEnergy, VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = PoundSquareInch.pressure(this, volume)

@JvmName("metricAndImperialEnergyDivImperialVolume")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = PoundSquareInch.pressure(this, volume)

@JvmName("imperialEnergyDivUKImperialVolume")
infix operator fun <EnergyUnit : ImperialEnergy, VolumeUnit : UKImperialVolume> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = PoundSquareInch.ukImperial.pressure(this, volume)

@JvmName("imperialEnergyDivUSCustomaryVolume")
infix operator fun <EnergyUnit : ImperialEnergy, VolumeUnit : USCustomaryVolume> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = PoundSquareInch.usCustomary.pressure(this, volume)

@JvmName("metricAndImperialEnergyDivUKImperialVolume")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, VolumeUnit : UKImperialVolume> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = PoundSquareInch.ukImperial.pressure(this, volume)

@JvmName("metricAndImperialEnergyDivUSCustomaryVolume")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, VolumeUnit : USCustomaryVolume> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = PoundSquareInch.usCustomary.pressure(this, volume)

@JvmName("energyDivVolume")
infix operator fun <EnergyUnit : Energy, VolumeUnit : Volume> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = Pascal.pressure(this, volume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.specificEnergy.specificEnergy
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.ImperialEnergy
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricAndImperialEnergy
import com.splendo.kaluga.scientific.unit.MetricEnergy
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricAndImperialEnergyDivMetricWeight")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificEnergy(this, weight)

@JvmName("metricAndImperialEnergyDivImperialWeight")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificEnergy(this, weight)

@JvmName("metricAndImperialEnergyDivUKImperialWeight")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificEnergy(this, weight)

@JvmName("metricAndImperialEnergyDivUSCustomaryWeight")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificEnergy(this, weight)

@JvmName("metricEnergyDivMetricWeight")
infix operator fun <EnergyUnit : MetricEnergy, WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificEnergy(this, weight)

@JvmName("imperialEnergyDivImperialWeight")
infix operator fun <EnergyUnit : ImperialEnergy, WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificEnergy(this, weight)

@JvmName("imperialEnergyDivUKImperialWeight")
infix operator fun <EnergyUnit : ImperialEnergy, WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificEnergy(this, weight)

@JvmName("imperialEnergyDivUSCustomaryWeight")
infix operator fun <EnergyUnit : ImperialEnergy, WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificEnergy(this, weight)

@JvmName("energyDivWeight")
infix operator fun <EnergyUnit : Energy, WeightUnit : Weight> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (Joule per Kilogram).specificEnergy(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.surfaceTension.surfaceTension
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Centimeter
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.Foot
import com.splendo.kaluga.scientific.unit.FootPoundal
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialEnergy
import com.splendo.kaluga.scientific.unit.Inch
import com.splendo.kaluga.scientific.unit.InchOunceForce
import com.splendo.kaluga.scientific.unit.InchPoundForce
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricAndImperialEnergy
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Newton
import com.splendo.kaluga.scientific.unit.OunceForce
import com.splendo.kaluga.scientific.unit.PoundForce
import com.splendo.kaluga.scientific.unit.Poundal
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.SquareFoot
import com.splendo.kaluga.scientific.unit.SquareInch
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("ergDivSquareCentimeter")
infix operator fun ScientificValue<PhysicalQuantity.Energy, Erg>.div(area: ScientificValue<PhysicalQuantity.Area, SquareCentimeter>) =
    (Dyne per Centimeter).surfaceTension(this, area)

@JvmName("ergMultipleDivSquareCentimeter")
infix operator fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(area: ScientificValue<PhysicalQuantity.Area, SquareCentimeter>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    (Dyne per Centimeter).surfaceTension(this, area)

@JvmName("metricAndImperialEnergyDivMetricArea")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (Newton per Meter).surfaceTension(this, area)

@JvmName("footPoundalDivSquareFoot")
infix operator fun ScientificValue<PhysicalQuantity.Energy, FootPoundal>.div(area: ScientificValue<PhysicalQuantity.Area, SquareFoot>) =
    (Poundal per Foot).surfaceTension(this, area)

@JvmName("inchPoundForceDivSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchPoundForce>.div(area: ScientificValue<PhysicalQuantity.Area, SquareInch>) =
    (PoundForce per Inch).surfaceTension(this, area)

@JvmName("inchOunceForceDivSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchOunceForce>.div(area: ScientificValue<PhysicalQuantity.Area, SquareInch>) =
    (OunceForce per Inch).surfaceTension(this, area)

@JvmName("metricAndImperialEnergyDivImperialArea")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (PoundForce per Foot).surfaceTension(this, area)

@JvmName("imperialEnergyDivImperialArea")
infix operator fun <EnergyUnit : ImperialEnergy, AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (PoundForce per Foot).surfaceTension(this, area)

@JvmName("energyDivArea")
infix operator fun <EnergyUnit : Energy, AreaUnit : Area> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (Newton per Meter).surfaceTension(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.temperature.temperature
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.HeatCapacity
import com.splendo.kaluga.scientific.unit.ImperialEnergy
import com.splendo.kaluga.scientific.unit.MetricAndImperialEnergy
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialHeatCapacity
import com.splendo.kaluga.scientific.unit.MetricEnergy
import com.splendo.kaluga.scientific.unit.MetricHeatCapacity
import com.splendo.kaluga.scientific.unit.UKImperialHeatCapacity
import com.splendo.kaluga.scientific.unit.USCustomaryHeatCapacity
import kotlin.jvm.JvmName

@JvmName("metricAndImperialEnergyDivMetricAndUKImperialHeatCapacity")
infix operator fun <EnergyUnit : MetricAndImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, MetricAndUKImperialHeatCapacity>) =
    heatCapacity.unit.per.temperature(this, heatCapacity)

@JvmName("metricAndImperialEnergyDivMetricHeatCapacity")
infix operator fun <EnergyUnit : MetricAndImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, MetricHeatCapacity>) =
    heatCapacity.unit.per.temperature(this, heatCapacity)

@JvmName("metricAndImperialEnergyDivUKImperialHeatCapacity")
infix operator fun <EnergyUnit : MetricAndImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, UKImperialHeatCapacity>) =
    heatCapacity.unit.per.temperature(this, heatCapacity)

@JvmName("metricAndImperialEnergyDivUSCustomaryHeatCapacity")
infix operator fun <EnergyUnit : MetricAndImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, USCustomaryHeatCapacity>) =
    heatCapacity.unit.per.temperature(this, heatCapacity)

@JvmName("metricEnergyDivMetricHeatCapacity")
infix operator fun <EnergyUnit : MetricEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, MetricHeatCapacity>) =
    heatCapacity.unit.per.temperature(this, heatCapacity)

@JvmName("imperialEnergyDivMetricAndImperialHeatCapacity")
infix operator fun <EnergyUnit : ImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, MetricAndUKImperialHeatCapacity>) =
    heatCapacity.unit.per.temperature(this, heatCapacity)

@JvmName("imperialEnergyDivUKImperialHeatCapacity")
infix operator fun <EnergyUnit : ImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, UKImperialHeatCapacity>) =
    heatCapacity.unit.per.temperature(this, heatCapacity)

@JvmName("imperialEnergyDivUSCustomaryHeatCapacity")
infix operator fun <EnergyUnit : ImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, USCustomaryHeatCapacity>) =
    heatCapacity.unit.per.temperature(this, heatCapacity)

@JvmName("energyDivHeatCapacity")
infix operator fun <EnergyUnit : Energy, HeatCapacityUnit : HeatCapacity> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, HeatCapacityUnit>
) = heatCapacity.unit.per.temperature(this, heatCapacity)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.BritishThermalUnit
import com.splendo.kaluga.scientific.unit.BritishThermalUnitPerHour
import com.splendo.kaluga.scientific.unit.BritishThermalUnitPerMinute
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.FootPoundForce
import com.splendo.kaluga.scientific.unit.FootPoundForcePerMinute
import com.splendo.kaluga.scientific.unit.Horsepower
import com.splendo.kaluga.scientific.unit.HorsepowerHour
import com.splendo.kaluga.scientific.unit.Hour
import com.splendo.kaluga.scientific.unit.InchPoundForce
import com.splendo.kaluga.scientific.unit.InchPoundForcePerMinute
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Minute
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Watt
import com.splendo.kaluga.scientific.unit.WattHour
import kotlin.jvm.JvmName

@JvmName("wattHourDivWatt")
infix operator fun ScientificValue<PhysicalQuantity.Energy, WattHour>.div(power: ScientificValue<PhysicalQuantity.Power, Watt>) =
    Hour.time(this, power)

@JvmName("wattHourMultipleDivWatt")
infix operator fun <WattHourUnit> ScientificValue<PhysicalQuantity.Energy, WattHourUnit>.div(power: ScientificValue<PhysicalQuantity.Power, Watt>) where WattHourUnit : Energy, WattHourUnit : MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, WattHour> =
    Hour.time(this, power)

@JvmName("wattHourDivWattMultiple")
infix operator fun <WattUnit> ScientificValue<PhysicalQuantity.Energy, WattHour>.div(power: ScientificValue<PhysicalQuantity.Power, WattUnit>) where WattUnit : Power, WattUnit : MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Power, Watt> =
    Hour.time(this, power)

@JvmName("wattHourMultipleDivWattMultiple")
infix operator fun <WattHourUnit, WattUnit> ScientificValue<PhysicalQuantity.Energy, WattHourUnit>.div(
    power: ScientificValue<PhysicalQuantity.Power, WattUnit>
) where WattHourUnit : Energy, WattHourUnit : MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Energy, WattHour>, WattUnit : Power, WattUnit : MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.Power, Watt> =
    Hour.time(this, power)

@JvmName("horsepowerHourDivHorsepower")
infix operator fun ScientificValue<PhysicalQuantity.Energy, HorsepowerHour>.div(power: ScientificValue<PhysicalQuantity.Power, Horsepower>) =
    Hour.time(this, power)

@JvmName("footPoundForceDivFootPoundForcePerMinute")
infix operator fun ScientificValue<PhysicalQuantity.Energy, FootPoundForce>.div(power: ScientificValue<PhysicalQuantity.Power, FootPoundForcePerMinute>) =
    Minute.time(this, power)

@JvmName("inchPoundForceDivInchPoundForcePerMinute")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchPoundForce>.div(power: ScientificValue<PhysicalQuantity.Power, InchPoundForcePerMinute>) =
    Minute.time(this, power)

@JvmName("britishThermalUnitDivBritishThermalUnitPerHour")
infix operator fun ScientificValue<PhysicalQuantity.Energy, BritishThermalUnit>.div(power: ScientificValue<PhysicalQuantity.Power, BritishThermalUnitPerHour>) =
    Hour.time(this, power)

@JvmName("britishThermalUnitDivBritishThermalUnitPerMinute")
infix operator fun ScientificValue<PhysicalQuantity.Energy, BritishThermalUnit>.div(power: ScientificValue<PhysicalQuantity.Power, BritishThermalUnitPerMinute>) =
    Minute.time(this, power)

@JvmName("energyDivPower")
infix operator fun <EnergyUnit : Energy, PowerUnit : Power> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = Second.time(this, power)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.voltage.voltage
import com.splendo.kaluga.scientific.unit.Abcoulomb
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Volt
import kotlin.jvm.JvmName

@JvmName("ergDivAbcoulomb")
infix operator fun ScientificValue<PhysicalQuantity.Energy, Erg>.div(charge: ScientificValue<PhysicalQuantity.ElectricCharge, Abcoulomb>) =
    Abvolt.voltage(this, charge)

@JvmName("ergMultipleDivAbcoulomb")
infix operator fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(charge: ScientificValue<PhysicalQuantity.ElectricCharge, Abcoulomb>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    Abvolt.voltage(this, charge)

@JvmName("energyDivCharge")
infix operator fun <EnergyUnit : Energy, ChargeUnit : ElectricCharge> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>
) = Volt.voltage(this, charge)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.volume.volume
import com.splendo.kaluga.scientific.unit.Barye
import com.splendo.kaluga.scientific.unit.CubicCentimeter
import com.splendo.kaluga.scientific.unit.CubicFoot
import com.splendo.kaluga.scientific.unit.CubicInch
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.FootPoundal
import com.splendo.kaluga.scientific.unit.ImperialEnergy
import com.splendo.kaluga.scientific.unit.ImperialPressure
import com.splendo.kaluga.scientific.unit.InchOunceForce
import com.splendo.kaluga.scientific.unit.InchPoundForce
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricAndImperialEnergy
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.PoundSquareFoot
import com.splendo.kaluga.scientific.unit.PoundSquareInch
import com.splendo.kaluga.scientific.unit.Pressure
import com.splendo.kaluga.scientific.unit.UKImperialPressure
import com.splendo.kaluga.scientific.unit.USCustomaryPressure
import com.splendo.kaluga.scientific.unit.ukImperial
import com.splendo.kaluga.scientific.unit.usCustomary
import kotlin.jvm.JvmName

@JvmName("ergDivBarye")
infix operator fun ScientificValue<PhysicalQuantity.Energy, Erg>.div(pressure: ScientificValue<PhysicalQuantity.Pressure, Barye>) =
    CubicCentimeter.volume(this, pressure)

@JvmName("ergMultipleDivBarye")
infix operator fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(pressure: ScientificValue<PhysicalQuantity.Pressure, Barye>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    CubicCentimeter.volume(this, pressure)

@JvmName("ergDivBaryeMultiple")
infix operator fun <BaryeUnit> ScientificValue<PhysicalQuantity.Energy, Erg>.div(pressure: ScientificValue<PhysicalQuantity.Pressure, BaryeUnit>) where BaryeUnit : Pressure, BaryeUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> =
    CubicCentimeter.volume(this, pressure)

@JvmName("ergMultipleDivBaryeMultiple")
infix operator fun <ErgUnit, BaryeUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, BaryeUnit>
) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg>, BaryeUnit : Pressure, BaryeUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> =
    CubicCentimeter.volume(this, pressure)

@JvmName("footPoundalDivPoundSquareFoot")
infix operator fun ScientificValue<PhysicalQuantity.Energy, FootPoundal>.div(pressure: ScientificValue<PhysicalQuantity.Pressure, PoundSquareFoot>) =
    CubicFoot.volume(this, pressure)

@JvmName("inchPoundForceDivPoundSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchPoundForce>.div(pressure: ScientificValue<PhysicalQuantity.Pressure, PoundSquareInch>) =
    CubicInch.volume(this, pressure)

@JvmName("inchOunceForceDivPoundSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Energy, InchOunceForce>.div(pressure: ScientificValue<PhysicalQuantity.Pressure, PoundSquareInch>) =
    CubicInch.volume(this, pressure)

@JvmName("imperialEnergyDivImperialPressure")
infix operator fun <EnergyUnit : ImperialEnergy, PressureUnit : ImperialPressure> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = CubicFoot.volume(this, pressure)

@JvmName("metricAndImperialEnergyDivImperialPressure")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, PressureUnit : ImperialPressure> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = CubicFoot.volume(this, pressure)

@JvmName("imperialEnergyDivUKImperialPressure")
infix operator fun <EnergyUnit : ImperialEnergy, PressureUnit : UKImperialPressure> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = CubicFoot.ukImperial.volume(this, pressure)

@JvmName("metricAndImperialEnergyDivUKImperialPressure")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, PressureUnit : UKImperialPressure> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = CubicFoot.ukImperial.volume(this, pressure)

@JvmName("imperialEnergyDivUSCustomaryPressure")
infix operator fun <EnergyUnit : ImperialEnergy, PressureUnit : USCustomaryPressure> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = CubicFoot.usCustomary.volume(this, pressure)

@JvmName("metricAndImperialEnergyDivUSCustomaryPressure")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, PressureUnit : USCustomaryPressure> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = CubicFoot.usCustomary.volume(this, pressure)

@JvmName("energyDivPressure")
infix operator fun <EnergyUnit : Energy, PressureUnit : Pressure> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = CubicMeter.volume(this, pressure)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.energy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.weight.weight
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.Gram
import com.splendo.kaluga.scientific.unit.ImperialEnergy
import com.splendo.kaluga.scientific.unit.ImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.IonizingRadiationAbsorbedDose
import com.splendo.kaluga.scientific.unit.IonizingRadiationEquivalentDose
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricAndImperialEnergy
import com.splendo.kaluga.scientific.unit.MetricEnergy
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.MetricSpecificEnergy
import com.splendo.kaluga.scientific.unit.Rad
import com.splendo.kaluga.scientific.unit.RoentgenEquivalentMan
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.UKImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.USCustomarySpecificEnergy
import kotlin.jvm.JvmName

@JvmName("ergDivRad")
infix operator fun ScientificValue<PhysicalQuantity.Energy, Erg>.div(absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad>) =
    Gram.weight(this, absorbedDose)

@JvmName("ergMultipleDivRad")
infix operator fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    Gram.weight(this, absorbedDose)

@JvmName("ergMultipleDivRadMultiple")
infix operator fun <AbsorbedDoseUnit> ScientificValue<PhysicalQuantity.Energy, Erg>.div(
    absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>
) where AbsorbedDoseUnit : IonizingRadiationAbsorbedDose, AbsorbedDoseUnit : MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad> =
    Gram.weight(this, absorbedDose)

@JvmName("ergMultipleDivAbsorbedDose")
infix operator fun <ErgUnit, AbsorbedDoseUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(
    absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>
) where ErgUnit : Energy,
    ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg>,
    AbsorbedDoseUnit : IonizingRadiationAbsorbedDose,
    AbsorbedDoseUnit : MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad> =
    Gram.weight(this, absorbedDose)

@JvmName("energyDivAbsorbedDose")
infix operator fun <EnergyUnit : Energy, AbsorbedDoseUnit : IonizingRadiationAbsorbedDose> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>
) = Kilogram.weight(this, absorbedDose)

@JvmName("ergDivRem")
infix operator fun ScientificValue<PhysicalQuantity.Energy, Erg>.div(equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan>) =
    Gram.weight(this, equivalentDose)

@JvmName("ergMultipleDivRem")
infix operator fun <ErgUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan>) where ErgUnit : Energy, ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg> =
    Gram.weight(this, equivalentDose)

@JvmName("ergDivRemMultiple")
infix operator fun <EquivalentDoseUnit> ScientificValue<PhysicalQuantity.Energy, Erg>.div(
    equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>
) where EquivalentDoseUnit : IonizingRadiationEquivalentDose,
    EquivalentDoseUnit : MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan> =
    Gram.weight(this, equivalentDose)

@JvmName("ergMultipleDivRemMultiple")
infix operator fun <ErgUnit, EquivalentDoseUnit> ScientificValue<PhysicalQuantity.Energy, ErgUnit>.div(
    equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>
) where ErgUnit : Energy,
    ErgUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Energy, Erg>,
    EquivalentDoseUnit : IonizingRadiationEquivalentDose,
    EquivalentDoseUnit : MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan> =
    Gram.weight(this, equivalentDose)

@JvmName("energyDivEquivalentDose")
infix operator fun <EnergyUnit : Energy, EquivalentDoseUnit : IonizingRadiationEquivalentDose> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>
) = Kilogram.weight(this, equivalentDose)

@JvmName("metricAndImperialEnergyDivMetricSpecificEnergy")
infix operator fun <EnergyUnit : MetricAndImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, MetricSpecificEnergy>
) = specificEnergy.unit.per.weight(this, specificEnergy)

@JvmName("metricAndImperialEnergyDivImperialSpecificEnergy")
infix operator fun <EnergyUnit : MetricAndImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>
) = specificEnergy.unit.per.weight(this, specificEnergy)

@JvmName("metricAndImperialEnergyDivUKImperialSpecificEnergy")
infix operator fun <EnergyUnit : MetricAndImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, UKImperialSpecificEnergy>
) = specificEnergy.unit.per.weight(this, specificEnergy)

@JvmName("metricAndImperialEnergyDivUSCustomarySpecificEnergy")
infix operator fun <EnergyUnit : MetricAndImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, USCustomarySpecificEnergy>
) = specificEnergy.unit.per.weight(this, specificEnergy)

@JvmName("metricEnergyDivMetricSpecificEnergy")
infix operator fun <EnergyUnit : MetricEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, MetricSpecificEnergy>) =
    specificEnergy.unit.per.weight(this, specificEnergy)

@JvmName("imperialEnergyDivImperialSpecificEnergy")
infix operator fun <EnergyUnit : ImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>) =
    specificEnergy.unit.per.weight(this, specificEnergy)

@JvmName("imperialEnergyDivUKImperialSpecificEnergy")
infix operator fun <EnergyUnit : ImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, UKImperialSpecificEnergy>) =
    specificEnergy.unit.per.weight(this, specificEnergy)

@JvmName("imperialEnergyDivUSCustomarySpecificEnergy")
infix operator fun <EnergyUnit : ImperialEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, USCustomarySpecificEnergy>) =
    specificEnergy.unit.per.weight(this, specificEnergy)

@JvmName("energyDivSpecificEnergy")
infix operator fun <EnergyUnit : Energy, SpecificEnergyUnit : SpecificEnergy> ScientificValue<PhysicalQuantity.Energy, EnergyUnit>.div(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>
) = specificEnergy.unit.per.weight(this, specificEnergy)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Length
import kotlin.jvm.JvmName

@JvmName("forceFromEnergyAndLengthDefault")
fun <
    EnergyUnit : Energy,
    LengthUnit : Length,
    ForceUnit : Force
    > ForceUnit.force(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = force(energy, length, ::DefaultScientificValue)

@JvmName("forceFromEnergyAndLength")
fun <
    EnergyUnit : Energy,
    LengthUnit : Length,
    ForceUnit : Force,
    Value : ScientificValue<PhysicalQuantity.Force, ForceUnit>
    > ForceUnit.force(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    factory: (Decimal, ForceUnit) -> Value
) = byDividing(energy, length, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Momentum
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("forceFromMomentumAndTimeDefault")
fun <
    ForceUnit : Force,
    TimeUnit : Time,
    MomentumUnit : Momentum
    > ForceUnit.force(
    momentum: ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = force(momentum, time, ::DefaultScientificValue)

@JvmName("forceFromMomentumAndTime")
fun <
    ForceUnit : Force,
    TimeUnit : Time,
    MomentumUnit : Momentum,
    Value : ScientificValue<PhysicalQuantity.Force, ForceUnit>
    > ForceUnit.force(
    momentum: ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, ForceUnit) -> Value
) = byDividing(momentum, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Speed
import kotlin.jvm.JvmName

@JvmName("forceFromPowerAndSpeedDefault")
fun <
    ForceUnit : Force,
    SpeedUnit : Speed,
    PowerUnit : Power
    > ForceUnit.force(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>,
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>
) = force(power, speed, ::DefaultScientificValue)

@JvmName("forceFromPowerAndSpeed")
fun <
    ForceUnit : Force,
    SpeedUnit : Speed,
    PowerUnit : Power,
    Value : ScientificValue<PhysicalQuantity.Force, ForceUnit>
    > ForceUnit.force(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>,
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>,
    factory: (Decimal, ForceUnit) -> Value
) = byDividing(power, speed, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Pressure
import kotlin.jvm.JvmName

@JvmName("forceFromPressureAndAreaDefault")
fun <
    ForceUnit : Force,
    AreaUnit : Area,
    PressureUnit : Pressure
    > ForceUnit.force(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = force(pressure, area, ::DefaultScientificValue)

@JvmName("forceFromPressureAndArea")
fun <
    ForceUnit : Force,
    AreaUnit : Area,
    PressureUnit : Pressure,
    Value : ScientificValue<PhysicalQuantity.Force, ForceUnit>
    > ForceUnit.force(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, ForceUnit) -> Value
) = byMultiplying(pressure, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.SurfaceTension
import kotlin.jvm.JvmName

@JvmName("forceFromSurfaceTensionAndLengthDefault")
fun <
    ForceUnit : Force,
    LengthUnit : Length,
    SurfaceTensionUnit : SurfaceTension
    > ForceUnit.force(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = force(surfaceTension, length, ::DefaultScientificValue)

@JvmName("forceFromSurfaceTensionAndLength")
fun <
    ForceUnit : Force,
    LengthUnit : Length,
    SurfaceTensionUnit : SurfaceTension,
    Value : ScientificValue<PhysicalQuantity.Force, ForceUnit>
    > ForceUnit.force(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    factory: (Decimal, ForceUnit) -> Value
) = byMultiplying(surfaceTension, length, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("forceFromMassAndAccelerationDefault")
fun <
    MassUnit : Weight,
    AccelerationUnit : Acceleration,
    ForceUnit : Force
    > ForceUnit.force(
    mass: ScientificValue<PhysicalQuantity.Weight, MassUnit>,
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>
) = force(mass, acceleration, ::DefaultScientificValue)

@JvmName("forceFromMassAndAcceleration")
fun <
    MassUnit : Weight,
    AccelerationUnit : Acceleration,
    ForceUnit : Force,
    Value : ScientificValue<PhysicalQuantity.Force, ForceUnit>
    > ForceUnit.force(
    mass: ScientificValue<PhysicalQuantity.Weight, MassUnit>,
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>,
    factory: (Decimal, ForceUnit) -> Value
) = byMultiplying(mass, acceleration, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Yank
import kotlin.jvm.JvmName

@JvmName("forceFromYankAndTimeDefault")
fun <
    ForceUnit : Force,
    TimeUnit : Time,
    YankUnit : Yank
    > ForceUnit.force(
    yank: ScientificValue<PhysicalQuantity.Yank, YankUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = force(yank, time, ::DefaultScientificValue)

@JvmName("forceFromYankAndTime")
fun <
    ForceUnit : Force,
    TimeUnit : Time,
    YankUnit : Yank,
    Value : ScientificValue<PhysicalQuantity.Force, ForceUnit>
    > ForceUnit.force(
    yank: ScientificValue<PhysicalQuantity.Yank, YankUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, ForceUnit) -> Value
) = byMultiplying(yank, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.acceleration.acceleration
import com.splendo.kaluga.scientific.unit.Centimeter
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.Foot
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Gram
import com.splendo.kaluga.scientific.unit.ImperialForce
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.UKImperialForce
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryForce
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("dyneDivGram")
infix operator fun ScientificValue<PhysicalQuantity.Force, Dyne>.div(mass: ScientificValue<PhysicalQuantity.Weight, Gram>) =
    (Centimeter per Second per Second).acceleration(this, mass)

@JvmName("dyneMultipleDivGram")
infix operator fun <DyneUnit> ScientificValue<PhysicalQuantity.Force, DyneUnit>.div(mass: ScientificValue<PhysicalQuantity.Weight, Gram>) where DyneUnit : Force, DyneUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> =
    (Centimeter per Second per Second).acceleration(this, mass)

@JvmName("imperialForceDivImperialWeight")
infix operator fun <ForceUnit : ImperialForce, WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (Foot per Second per Second).acceleration(this, mass)

@JvmName("imperialForceDivUKImperialWeight")
infix operator fun <ForceUnit : ImperialForce, WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (Foot per Second per Second).acceleration(this, mass)

@JvmName("imperialForceDivUSCustomaryWeight")
infix operator fun <ForceUnit : ImperialForce, WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (Foot per Second per Second).acceleration(this, mass)

@JvmName("ukImperialForceDivImperialWeight")
infix operator fun <ForceUnit : UKImperialForce, WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (Foot per Second per Second).acceleration(this, mass)

@JvmName("ukImperialForceDivUKImperialWeight")
infix operator fun <ForceUnit : UKImperialForce, WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (Foot per Second per Second).acceleration(this, mass)

@JvmName("usCustomaryForceDivImperialWeight")
infix operator fun <ForceUnit : USCustomaryForce, WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (Foot per Second per Second).acceleration(this, mass)

@JvmName("usCustomaryForceDivUSCustomaryWeight")
infix operator fun <ForceUnit : USCustomaryForce, WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (Foot per Second per Second).acceleration(this, mass)

@JvmName("forceDivWeight")
infix operator fun <ForceUnit : Force, WeightUnit : Weight> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (Meter per Second per Second).acceleration(this, mass)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.area
import com.splendo.kaluga.scientific.unit.Barye
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.ImperialForce
import com.splendo.kaluga.scientific.unit.ImperialPressure
import com.splendo.kaluga.scientific.unit.ImperialTonForce
import com.splendo.kaluga.scientific.unit.ImperialTonSquareFoot
import com.splendo.kaluga.scientific.unit.ImperialTonSquareInch
import com.splendo.kaluga.scientific.unit.KiloPoundSquareInch
import com.splendo.kaluga.scientific.unit.Kip
import com.splendo.kaluga.scientific.unit.KipSquareFoot
import com.splendo.kaluga.scientific.unit.KipSquareInch
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.OunceForce
import com.splendo.kaluga.scientific.unit.OunceSquareInch
import com.splendo.kaluga.scientific.unit.PoundForce
import com.splendo.kaluga.scientific.unit.PoundSquareFoot
import com.splendo.kaluga.scientific.unit.PoundSquareInch
import com.splendo.kaluga.scientific.unit.Pressure
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.SquareFoot
import com.splendo.kaluga.scientific.unit.SquareInch
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.UKImperialForce
import com.splendo.kaluga.scientific.unit.UKImperialPressure
import com.splendo.kaluga.scientific.unit.USCustomaryForce
import com.splendo.kaluga.scientific.unit.USCustomaryPressure
import com.splendo.kaluga.scientific.unit.USTonSquareFoot
import com.splendo.kaluga.scientific.unit.USTonSquareInch
import com.splendo.kaluga.scientific.unit.UsTonForce
import kotlin.jvm.JvmName

@JvmName("dyneDivBarye")
infix operator fun ScientificValue<PhysicalQuantity.Force, Dyne>.div(pressure: ScientificValue<PhysicalQuantity.Pressure, Barye>) =
    SquareCentimeter.area(this, pressure)

@JvmName("dyneDivBaryeMultiple")
infix operator fun <BaryeUnit> ScientificValue<PhysicalQuantity.Force, Dyne>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, BaryeUnit>
) where BaryeUnit : Pressure, BaryeUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> =
    SquareCentimeter.area(this, pressure)

@JvmName("dyneMultipleDivBarye")
infix operator fun <DyneUnit> ScientificValue<PhysicalQuantity.Force, DyneUnit>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, Barye>
) where DyneUnit : Force, DyneUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> =
    SquareCentimeter.area(this, pressure)

@JvmName("dyneMultipleDivBaryeMultiple")
infix operator fun <DyneUnit, BaryeUnit> ScientificValue<PhysicalQuantity.Force, DyneUnit>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, BaryeUnit>
) where DyneUnit : Force, DyneUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne>, BaryeUnit : Pressure, BaryeUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> =
    SquareCentimeter.area(this, pressure)

@JvmName("poundForcePoundSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Force, PoundForce>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PoundSquareInch>
) = SquareInch.area(this, pressure)

@JvmName("poundForceDivPoundSquareFeet")
infix operator fun ScientificValue<PhysicalQuantity.Force, PoundForce>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PoundSquareFoot>
) = SquareFoot.area(this, pressure)

@JvmName("poundForceDivKiloPoundSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Force, PoundForce>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, KiloPoundSquareInch>
) = SquareInch.area(this, pressure)

@JvmName("ounceForceDivOunceSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Force, OunceForce>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, OunceSquareInch>
) = SquareInch.area(this, pressure)

@JvmName("kipDivKipSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Force, Kip>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, KipSquareInch>
) = SquareInch.area(this, pressure)

@JvmName("kipDivPoundSquareFeet")
infix operator fun ScientificValue<PhysicalQuantity.Force, Kip>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, KipSquareFoot>
) = SquareFoot.area(this, pressure)

@JvmName("usTonForceDivUSTonSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Force, UsTonForce>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, USTonSquareInch>
) = SquareInch.area(this, pressure)

@JvmName("usTonForceDivUSTonSquareFeet")
infix operator fun ScientificValue<PhysicalQuantity.Force, UsTonForce>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, USTonSquareFoot>
) = SquareFoot.area(this, pressure)

@JvmName("imperialTonForceDivImperialTonSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Force, ImperialTonForce>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, ImperialTonSquareInch>
) = SquareInch.area(this, pressure)

@JvmName("imperialTonForceDivImperialTonSquareFeet")
infix operator fun ScientificValue<PhysicalQuantity.Force, ImperialTonForce>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, ImperialTonSquareFoot>
) = SquareFoot.area(this, pressure)

@JvmName("imperialForceDivImperialPressure")
infix operator fun <Force : ImperialForce, Pressure : ImperialPressure> ScientificValue<PhysicalQuantity.Force, Force>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, Pressure>
) = SquareFoot.area(this, pressure)

@JvmName("imperialForceDivUKImperialPressure")
infix operator fun <Force : ImperialForce, Pressure : UKImperialPressure> ScientificValue<PhysicalQuantity.Force, Force>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, Pressure>
) = SquareFoot.area(this, pressure)

@JvmName("imperialForceDivUSCustomaryPressure")
infix operator fun <Force : ImperialForce, Pressure : USCustomaryPressure> ScientificValue<PhysicalQuantity.Force, Force>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, Pressure>
) = SquareFoot.area(this, pressure)

@JvmName("ukImperialForceDivImperialPressure")
infix operator fun <Force : UKImperialForce, Pressure : ImperialPressure> ScientificValue<PhysicalQuantity.Force, Force>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, Pressure>
) = SquareFoot.area(this, pressure)

@JvmName("ukImperialForceDivUKImperialPressure")
infix operator fun <Force : UKImperialForce, Pressure : UKImperialPressure> ScientificValue<PhysicalQuantity.Force, Force>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, Pressure>
) = SquareFoot.area(this, pressure)

@JvmName("usCustomaryForceDivImperialPressure")
infix operator fun <Force : USCustomaryForce, Pressure : ImperialPressure> ScientificValue<PhysicalQuantity.Force, Force>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, Pressure>
) = SquareFoot.area(this, pressure)

@JvmName("usCustomaryForceDivUSCustomaryPressure")
infix operator fun <Force : USCustomaryForce, Pressure : USCustomaryPressure> ScientificValue<PhysicalQuantity.Force, Force>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, Pressure>
) = SquareFoot.area(this, pressure)

@JvmName("forceDivPressure")
infix operator fun <ForceUnit : Force, PressureUnit : Pressure> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = SquareMeter.area(this, pressure)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.energy.energy
import com.splendo.kaluga.scientific.unit.Centimeter
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.FootPoundForce
import com.splendo.kaluga.scientific.unit.FootPoundal
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.ImperialForce
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.Inch
import com.splendo.kaluga.scientific.unit.InchOunceForce
import com.splendo.kaluga.scientific.unit.InchPoundForce
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricForce
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.OunceForce
import com.splendo.kaluga.scientific.unit.PoundForce
import com.splendo.kaluga.scientific.unit.Poundal
import com.splendo.kaluga.scientific.unit.UKImperialForce
import com.splendo.kaluga.scientific.unit.USCustomaryForce
import kotlin.jvm.JvmName

@JvmName("dyneTimesCentimeter")
infix operator fun ScientificValue<PhysicalQuantity.Force, Dyne>.times(distance: ScientificValue<PhysicalQuantity.Length, Centimeter>) =
    Erg.energy(this, distance)

@JvmName("dyneMultipleTimesCentimeter")
infix operator fun <DyneUnit> ScientificValue<PhysicalQuantity.Force, DyneUnit>.times(distance: ScientificValue<PhysicalQuantity.Length, Centimeter>) where DyneUnit : Force, DyneUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> =
    Erg.energy(this, distance)

@JvmName("metricForceTimesMetricLength")
infix operator fun <ForceUnit : MetricForce, LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.Force, ForceUnit>.times(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = Joule.energy(this, distance)

@JvmName("poundalTimesImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Force, Poundal>.times(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = FootPoundal.energy(this, distance)

@JvmName("poundForceTimesInch")
infix operator fun ScientificValue<PhysicalQuantity.Force, PoundForce>.times(distance: ScientificValue<PhysicalQuantity.Length, Inch>) =
    InchPoundForce.energy(this, distance)

@JvmName("ounceForceTimesInch")
infix operator fun ScientificValue<PhysicalQuantity.Force, OunceForce>.times(distance: ScientificValue<PhysicalQuantity.Length, Inch>) =
    InchOunceForce.energy(this, distance)

@JvmName("poundForceTimesImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Force, PoundForce>.times(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = FootPoundForce.energy(this, distance)

@JvmName("imperialForceTimesImperialLength")
infix operator fun <ForceUnit : ImperialForce, LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Force, ForceUnit>.times(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = FootPoundForce.energy(this, distance)

@JvmName("ukImperialForceTimesImperialLength")
infix operator fun <ForceUnit : UKImperialForce, LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Force, ForceUnit>.times(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = FootPoundForce.energy(this, distance)

@JvmName("usCustomaryForceTimesImperialLength")
infix operator fun <ForceUnit : USCustomaryForce, LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Force, ForceUnit>.times(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = FootPoundForce.energy(this, distance)

@JvmName("forceTimesLength")
infix operator fun <ForceUnit : Force, LengthUnit : Length> ScientificValue<PhysicalQuantity.Force, ForceUnit>.times(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = Joule.energy(this, distance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.length.length
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.ImperialForce
import com.splendo.kaluga.scientific.unit.ImperialSurfaceTension
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricForce
import com.splendo.kaluga.scientific.unit.MetricSurfaceTension
import com.splendo.kaluga.scientific.unit.SurfaceTension
import com.splendo.kaluga.scientific.unit.UKImperialForce
import com.splendo.kaluga.scientific.unit.UKImperialSurfaceTension
import com.splendo.kaluga.scientific.unit.USCustomaryForce
import com.splendo.kaluga.scientific.unit.USCustomarySurfaceTension
import kotlin.jvm.JvmName

@JvmName("metricForceDivMetricSurfaceTension")
infix operator fun <ForceUnit : MetricForce> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, MetricSurfaceTension>
) = surfaceTension.unit.per.length(this, surfaceTension)

@JvmName("imperialForceDivImperialSurfaceTension")
infix operator fun <ForceUnit : ImperialForce> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, ImperialSurfaceTension>
) = surfaceTension.unit.per.length(this, surfaceTension)

@JvmName("imperialForceDivUKImperialSurfaceTension")
infix operator fun <ForceUnit : ImperialForce> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, UKImperialSurfaceTension>
) = surfaceTension.unit.per.length(this, surfaceTension)

@JvmName("imperialForceDivUSCustomarySurfaceTension")
infix operator fun <ForceUnit : ImperialForce> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, USCustomarySurfaceTension>
) = surfaceTension.unit.per.length(this, surfaceTension)

@JvmName("ukImperialForceDivImperialSurfaceTension")
infix operator fun <ForceUnit : UKImperialForce> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, ImperialSurfaceTension>
) = surfaceTension.unit.per.length(this, surfaceTension)

@JvmName("ukImperialForceDivUKImperialSurfaceTension")
infix operator fun <ForceUnit : UKImperialForce> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, UKImperialSurfaceTension>
) = surfaceTension.unit.per.length(this, surfaceTension)

@JvmName("usCustomaryForceDivImperialSurfaceTension")
infix operator fun <ForceUnit : USCustomaryForce> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, ImperialSurfaceTension>
) = surfaceTension.unit.per.length(this, surfaceTension)

@JvmName("usCustomaryForceDivUSCustomarySurfaceTension")
infix operator fun <ForceUnit : USCustomaryForce> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, USCustomarySurfaceTension>
) = surfaceTension.unit.per.length(this, surfaceTension)

@JvmName("forceDivSurfaceTension")
infix operator fun <ForceUnit : Force, SurfaceTensionUnit : SurfaceTension> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>
) = Meter.length(this, surfaceTension)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.momentum.momentum
import com.splendo.kaluga.scientific.unit.Centimeter
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.Foot
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Grain
import com.splendo.kaluga.scientific.unit.GrainForce
import com.splendo.kaluga.scientific.unit.Gram
import com.splendo.kaluga.scientific.unit.GramForce
import com.splendo.kaluga.scientific.unit.ImperialForce
import com.splendo.kaluga.scientific.unit.ImperialTon
import com.splendo.kaluga.scientific.unit.ImperialTonForce
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricForce
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Milligram
import com.splendo.kaluga.scientific.unit.MilligramForce
import com.splendo.kaluga.scientific.unit.Ounce
import com.splendo.kaluga.scientific.unit.OunceForce
import com.splendo.kaluga.scientific.unit.Pound
import com.splendo.kaluga.scientific.unit.Poundal
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Tonne
import com.splendo.kaluga.scientific.unit.TonneForce
import com.splendo.kaluga.scientific.unit.UKImperialForce
import com.splendo.kaluga.scientific.unit.USCustomaryForce
import com.splendo.kaluga.scientific.unit.UsTon
import com.splendo.kaluga.scientific.unit.UsTonForce
import com.splendo.kaluga.scientific.unit.per
import com.splendo.kaluga.scientific.unit.x
import kotlin.jvm.JvmName

@JvmName("dyneTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, Dyne>.times(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    (Gram x (Centimeter per Second)).momentum(this, time)

@JvmName("dyneMultipleTimesTime")
infix operator fun <DyneUnit, TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, DyneUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) where DyneUnit : MetricForce, DyneUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> =
    (Gram x (Centimeter per Second)).momentum(this, time)

@JvmName("tonneForceTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, TonneForce>.times(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    (Tonne x (Meter per Second)).momentum(this, time)

@JvmName("gramForceTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, GramForce>.times(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    (Gram x (Meter per Second)).momentum(this, time)

@JvmName("milligramForceTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, MilligramForce>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (Milligram x (Meter per Second)).momentum(this, time)

@JvmName("metricForceTimesTime")
infix operator fun <ForceUnit : MetricForce, TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, ForceUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (Kilogram x (Meter per Second)).momentum(this, time)

@JvmName("poundalTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, Poundal>.times(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    (Pound x (Foot per Second)).momentum(this, time)

@JvmName("ounceForceTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, OunceForce>.times(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    (Ounce x (Foot per Second)).momentum(this, time)

@JvmName("grainForceTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, GrainForce>.times(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    (Grain x (Foot per Second)).momentum(this, time)

@JvmName("usTonForceTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, UsTonForce>.times(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    (UsTon x (Foot per Second)).momentum(this, time)

@JvmName("imperialTonForceTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, ImperialTonForce>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (ImperialTon x (Foot per Second)).momentum(this, time)

@JvmName("imperialForceTimesTime")
infix operator fun <ForceUnit : ImperialForce, TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, ForceUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (Pound x (Foot per Second)).momentum(this, time)

@JvmName("ukImperialForceTimesTime")
infix operator fun <ForceUnit : UKImperialForce, TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, ForceUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (Pound x (Foot per Second)).ukImperial.momentum(this, time)

@JvmName("usCustomaryForceTimesTime")
infix operator fun <ForceUnit : USCustomaryForce, TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, ForceUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (Pound x (Foot per Second)).usCustomary.momentum(this, time)

@JvmName("forceTimesTime")
infix operator fun <ForceUnit : Force, TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, ForceUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (Kilogram x (Meter per Second)).momentum(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.power.power
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.ErgPerSecond
import com.splendo.kaluga.scientific.unit.FootPoundForcePerSecond
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.ImperialForce
import com.splendo.kaluga.scientific.unit.ImperialSpeed
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.MetricSpeed
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.UKImperialForce
import com.splendo.kaluga.scientific.unit.USCustomaryForce
import com.splendo.kaluga.scientific.unit.Watt
import kotlin.jvm.JvmName

@JvmName("dyneTimesMetricSpeed")
infix operator fun ScientificValue<PhysicalQuantity.Force, Dyne>.times(speed: ScientificValue<PhysicalQuantity.Speed, MetricSpeed>) =
    ErgPerSecond.power(this, speed)

@JvmName("dyneMultipleTimesMetricSpeed")
infix operator fun <DyneUnit> ScientificValue<PhysicalQuantity.Force, DyneUnit>.times(speed: ScientificValue<PhysicalQuantity.Speed, MetricSpeed>) where DyneUnit : Force, DyneUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> =
    ErgPerSecond.power(this, speed)

@JvmName("imperialForceTimesImperialSpeed")
infix operator fun <ForceUnit : ImperialForce> ScientificValue<PhysicalQuantity.Force, ForceUnit>.times(
    speed: ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>
) = FootPoundForcePerSecond.power(this, speed)

@JvmName("ukImperialForceTimesImperialSpeed")
infix operator fun <ForceUnit : UKImperialForce> ScientificValue<PhysicalQuantity.Force, ForceUnit>.times(
    speed: ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>
) = FootPoundForcePerSecond.power(this, speed)

@JvmName("usCustomaryForceTimesImperialSpeed")
infix operator fun <ForceUnit : USCustomaryForce> ScientificValue<PhysicalQuantity.Force, ForceUnit>.times(
    speed: ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>
) = FootPoundForcePerSecond.power(this, speed)

@JvmName("forceTimesSpeed")
infix operator fun <ForceUnit : Force, SpeedUnit : Speed> ScientificValue<PhysicalQuantity.Force, ForceUnit>.times(
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>
) = Watt.power(this, speed)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.pressure.pressure
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Barye
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.GrainForce
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialForce
import com.splendo.kaluga.scientific.unit.ImperialTonForce
import com.splendo.kaluga.scientific.unit.ImperialTonSquareFoot
import com.splendo.kaluga.scientific.unit.ImperialTonSquareInch
import com.splendo.kaluga.scientific.unit.Kip
import com.splendo.kaluga.scientific.unit.KipSquareFoot
import com.splendo.kaluga.scientific.unit.KipSquareInch
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.OunceForce
import com.splendo.kaluga.scientific.unit.OunceSquareInch
import com.splendo.kaluga.scientific.unit.Pascal
import com.splendo.kaluga.scientific.unit.PoundForce
import com.splendo.kaluga.scientific.unit.PoundSquareFoot
import com.splendo.kaluga.scientific.unit.PoundSquareInch
import com.splendo.kaluga.scientific.unit.Poundal
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.SquareFoot
import com.splendo.kaluga.scientific.unit.UKImperialForce
import com.splendo.kaluga.scientific.unit.USCustomaryForce
import com.splendo.kaluga.scientific.unit.USTonSquareFoot
import com.splendo.kaluga.scientific.unit.USTonSquareInch
import com.splendo.kaluga.scientific.unit.UsTonForce
import com.splendo.kaluga.scientific.unit.ukImperial
import com.splendo.kaluga.scientific.unit.usCustomary
import kotlin.jvm.JvmName

@JvmName("dyneDivSquareCentimeter")
infix operator fun ScientificValue<PhysicalQuantity.Force, Dyne>.div(area: ScientificValue<PhysicalQuantity.Area, SquareCentimeter>) =
    Barye.pressure(this, area)

@JvmName("dyneMultipleDivSquareCentime")
infix operator fun <DyneUnit> ScientificValue<PhysicalQuantity.Force, DyneUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, SquareCentimeter>
) where DyneUnit : Force, DyneUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> =
    Barye.pressure(this, area)

@JvmName("poundalDivSquareFoot")
infix operator fun ScientificValue<PhysicalQuantity.Force, Poundal>.div(area: ScientificValue<PhysicalQuantity.Area, SquareFoot>) =
    PoundSquareFoot.pressure(this, area)

@JvmName("poundalDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Force, Poundal>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = PoundSquareInch.pressure(this, area)

@JvmName("poundForceDivSquareFoot")
infix operator fun ScientificValue<PhysicalQuantity.Force, PoundForce>.div(area: ScientificValue<PhysicalQuantity.Area, SquareFoot>) =
    PoundSquareFoot.pressure(this, area)

@JvmName("poundForceDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Force, PoundForce>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = PoundSquareInch.pressure(this, area)

@JvmName("ounceForceDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Force, OunceForce>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = OunceSquareInch.pressure(this, area)

@JvmName("grainForceDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Force, GrainForce>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = OunceSquareInch.pressure(this, area)

@JvmName("kipDivSquareFoot")
infix operator fun ScientificValue<PhysicalQuantity.Force, Kip>.div(area: ScientificValue<PhysicalQuantity.Area, SquareFoot>) =
    KipSquareFoot.pressure(this, area)

@JvmName("kipDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Force, Kip>.div(area: ScientificValue<PhysicalQuantity.Area, AreaUnit>) =
    KipSquareInch.pressure(this, area)

@JvmName("usTonForceDivSquareFoot")
infix operator fun ScientificValue<PhysicalQuantity.Force, UsTonForce>.div(area: ScientificValue<PhysicalQuantity.Area, SquareFoot>) =
    USTonSquareFoot.pressure(this, area)

@JvmName("usTonForceDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Force, UsTonForce>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = USTonSquareInch.pressure(this, area)

@JvmName("imperialTonForceDivSquareFoot")
infix operator fun ScientificValue<PhysicalQuantity.Force, ImperialTonForce>.div(area: ScientificValue<PhysicalQuantity.Area, SquareFoot>) =
    ImperialTonSquareFoot.pressure(this, area)

@JvmName("imperialTonForceDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Force, ImperialTonForce>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = ImperialTonSquareInch.pressure(this, area)

@JvmName("imperialForceDivImperialArea")
infix operator fun <ForceUnit : ImperialForce, AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = PoundSquareInch.pressure(this, area)

@JvmName("usCustomaryForceDivImperialArea")
infix operator fun <ForceUnit : USCustomaryForce, AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = PoundSquareInch.usCustomary.pressure(this, area)

@JvmName("ukImperialForceDivImperialArea")
infix operator fun <ForceUnit : UKImperialForce, AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = PoundSquareInch.ukImperial.pressure(this, area)

@JvmName("forceDivArea")
infix operator fun <ForceUnit : Force, AreaUnit : Area> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Pascal.pressure(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.surfaceTension.surfaceTension
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.ImperialForce
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricForce
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.Newton
import com.splendo.kaluga.scientific.unit.UKImperialForce
import com.splendo.kaluga.scientific.unit.USCustomaryForce
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricForceDivMetricLength")
infix operator fun <ForceUnit : MetricForce, LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit per length.unit).surfaceTension(this, length)

@JvmName("imperialForceDivImperialLength")
infix operator fun <ForceUnit : ImperialForce, LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit per length.unit).surfaceTension(this, length)

@JvmName("ukImperialForceDivImperialLength")
infix operator fun <ForceUnit : UKImperialForce, LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit per length.unit).surfaceTension(this, length)

@JvmName("usCustomaryForceDivImperialLength")
infix operator fun <ForceUnit : USCustomaryForce, LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit per length.unit).surfaceTension(this, length)

@JvmName("forceDivLength")
infix operator fun <ForceUnit : Force, LengthUnit : Length> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (Newton per Meter).surfaceTension(this, length)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Yank
import kotlin.jvm.JvmName

@JvmName("forceDivYank")
infix operator fun <ForceUnit : Force, YankUnit : Yank> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    yank: ScientificValue<PhysicalQuantity.Yank, YankUnit>
) = yank.unit.per.time(this, yank)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.weight.mass
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Grain
import com.splendo.kaluga.scientific.unit.GrainForce
import com.splendo.kaluga.scientific.unit.Gram
import com.splendo.kaluga.scientific.unit.GramForce
import com.splendo.kaluga.scientific.unit.ImperialAcceleration
import com.splendo.kaluga.scientific.unit.ImperialForce
import com.splendo.kaluga.scientific.unit.ImperialTon
import com.splendo.kaluga.scientific.unit.ImperialTonForce
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.KilogramForce
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricAcceleration
import com.splendo.kaluga.scientific.unit.MetricForce
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Milligram
import com.splendo.kaluga.scientific.unit.MilligramForce
import com.splendo.kaluga.scientific.unit.Ounce
import com.splendo.kaluga.scientific.unit.OunceForce
import com.splendo.kaluga.scientific.unit.Pound
import com.splendo.kaluga.scientific.unit.Tonne
import com.splendo.kaluga.scientific.unit.TonneForce
import com.splendo.kaluga.scientific.unit.UKImperialForce
import com.splendo.kaluga.scientific.unit.USCustomaryForce
import com.splendo.kaluga.scientific.unit.UsTon
import com.splendo.kaluga.scientific.unit.UsTonForce
import com.splendo.kaluga.scientific.unit.ukImperial
import com.splendo.kaluga.scientific.unit.usCustomary
import kotlin.jvm.JvmName

@JvmName("dyneDivAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.Force, Dyne>.div(acceleration: ScientificValue<PhysicalQuantity.Acceleration, MetricAcceleration>) =
    Gram.mass(this, acceleration)

@JvmName("dyneMultipleDivAcceleration")
infix operator fun <DyneUnit> ScientificValue<PhysicalQuantity.Force, DyneUnit>.div(acceleration: ScientificValue<PhysicalQuantity.Acceleration, MetricAcceleration>) where DyneUnit : MetricForce, DyneUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> =
    Gram.mass(this, acceleration)

@JvmName("kilogramForceDivAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.Force, KilogramForce>.div(acceleration: ScientificValue<PhysicalQuantity.Acceleration, MetricAcceleration>) =
    Kilogram.mass(this, acceleration)

@JvmName("gramForceDivAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.Force, GramForce>.div(acceleration: ScientificValue<PhysicalQuantity.Acceleration, MetricAcceleration>) =
    Gram.mass(this, acceleration)

@JvmName("milligramForceDivAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.Force, MilligramForce>.div(acceleration: ScientificValue<PhysicalQuantity.Acceleration, MetricAcceleration>) =
    Milligram.mass(this, acceleration)

@JvmName("tonneForceDivAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.Force, TonneForce>.div(acceleration: ScientificValue<PhysicalQuantity.Acceleration, MetricAcceleration>) =
    Tonne.mass(this, acceleration)

@JvmName("metricForceDivMetricAcceleration")
infix operator fun <ForceUnit : MetricForce> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, MetricAcceleration>
) = Kilogram.mass(this, acceleration)

@JvmName("ounceForceDivAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.Force, OunceForce>.div(acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>) =
    Ounce.mass(this, acceleration)

@JvmName("grainForceDivAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.Force, GrainForce>.div(acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>) =
    Grain.mass(this, acceleration)

@JvmName("usTonForceDivAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.Force, UsTonForce>.div(acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>) =
    UsTon.mass(this, acceleration)

@JvmName("imperialTonForceDivAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.Force, ImperialTonForce>.div(acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>) =
    ImperialTon.mass(this, acceleration)

@JvmName("imperialForceDivImperialAcceleration")
infix operator fun <ForceUnit : ImperialForce> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>
) = Pound.mass(this, acceleration)

@JvmName("ukImperialForceDivImperialAcceleration")
infix operator fun <ForceUnit : UKImperialForce> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>
) = Pound.ukImperial.mass(this, acceleration)

@JvmName("usCustomaryForceDivImperialAcceleration")
infix operator fun <ForceUnit : USCustomaryForce> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>
) = Pound.usCustomary.mass(this, acceleration)

@JvmName("forceDivAcceleration")
infix operator fun <ForceUnit : Force, AccelerationUnit : Acceleration> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>
) = Kilogram.mass(this, acceleration)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.force

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.yank.yank
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.ImperialForce
import com.splendo.kaluga.scientific.unit.MetricForce
import com.splendo.kaluga.scientific.unit.Newton
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.UKImperialForce
import com.splendo.kaluga.scientific.unit.USCustomaryForce
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricForceDivTime")
infix operator fun <ForceUnit : MetricForce, TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).yank(this, time)

@JvmName("imperialForceDivTime")
infix operator fun <ForceUnit : ImperialForce, TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).yank(this, time)

@JvmName("ukImperialForceDivTime")
infix operator fun <ForceUnit : UKImperialForce, TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).yank(this, time)

@JvmName("usCustomaryForceDivTime")
infix operator fun <ForceUnit : USCustomaryForce, TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).yank(this, time)

@JvmName("forceDivTime")
infix operator fun <ForceUnit : Force, TimeUnit : Time> ScientificValue<PhysicalQuantity.Force, ForceUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (Newton per time.unit).yank(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.frequency

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricInductance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Frequency
import kotlin.jvm.JvmName

@JvmName("frequencyFromResistanceAndInductanceDefault")
fun <
    ResistanceUnit : ElectricResistance,
    FrequencyUnit : Frequency,
    InductanceUnit : ElectricInductance
    > FrequencyUnit.frequency(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>,
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>
) = frequency(resistance, inductance, ::DefaultScientificValue)

@JvmName("frequencyFromResistanceAndInductance")
fun <
    ResistanceUnit : ElectricResistance,
    FrequencyUnit : Frequency,
    InductanceUnit : ElectricInductance,
    Value : ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
    > FrequencyUnit.frequency(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>,
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>,
    factory: (Decimal, FrequencyUnit) -> Value
) = byDividing(resistance, inductance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.frequency

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.ElectricConductance
import com.splendo.kaluga.scientific.unit.Frequency
import kotlin.jvm.JvmName

@JvmName("frequencyFromConductanceAndCapacityDefault")
fun <
    CapacitanceUnit : ElectricCapacitance,
    ConductanceUnit : ElectricConductance,
    FrequencyUnit : Frequency
    > FrequencyUnit.frequency(
    conductance: ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>,
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>
) = frequency(conductance, capacitance, ::DefaultScientificValue)

@JvmName("frequencyFromConductanceAndCapacity")
fun <
    CapacitanceUnit : ElectricCapacitance,
    ConductanceUnit : ElectricConductance,
    FrequencyUnit : Frequency,
    Value : ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
    > FrequencyUnit.frequency(
    conductance: ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>,
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>,
    factory: (Decimal, FrequencyUnit) -> Value
) = byDividing(conductance, capacitance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.frequency

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byInverting
import com.splendo.kaluga.scientific.convert
import com.splendo.kaluga.scientific.convertValue
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.Frequency
import com.splendo.kaluga.scientific.unit.Hertz
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("frequencyFromInvertedTimeDefault")
fun <
    TimeUnit : Time,
    FrequencyUnit : Frequency,
    > FrequencyUnit.frequency(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = frequency(time, ::DefaultScientificValue)

@JvmName("frequencyFromInvertedTime")
fun <
    TimeUnit : Time,
    FrequencyUnit : Frequency,
    Value : ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
    > FrequencyUnit.frequency(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, FrequencyUnit) -> Value
) = byInverting(time, factory)

fun <
    FrequencyUnit : Frequency,
    TimeUnit : Time
    > FrequencyUnit.frequency(
    cycle: Decimal,
    per: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = frequency(cycle, per, ::DefaultScientificValue)

fun <
    FrequencyUnit : Frequency,
    TimeUnit : Time,
    Value : ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
    > FrequencyUnit.frequency(
    cycle: Decimal,
    per: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, FrequencyUnit) -> Value
) = (cycle / per.convertValue(Second))(Hertz).convert(this, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.frequency

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.convertValue
import com.splendo.kaluga.scientific.unit.Frequency
import com.splendo.kaluga.scientific.unit.Hertz
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("frequencyTimesTime")
infix operator fun <FrequencyUnit : Frequency, TimeUnit : Time> ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
): Decimal = convertValue(Hertz) * time.convertValue(Second)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.frequency

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCapacitance.times
import com.splendo.kaluga.scientific.unit.Abfarad
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.Frequency
import kotlin.jvm.JvmName

@JvmName("frequencyTimesAbfarad")
infix operator fun <FrequencyUnit : Frequency> ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>.times(
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, Abfarad>
) = capacitance * this

@JvmName("frequencyTimesCapacitance")
infix operator fun <CapacitanceUnit : ElectricCapacitance, FrequencyUnit : Frequency> ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>.times(
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>
) = capacitance * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.frequency

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricInductance.times
import com.splendo.kaluga.scientific.unit.Abhenry
import com.splendo.kaluga.scientific.unit.ElectricInductance
import com.splendo.kaluga.scientific.unit.Frequency
import kotlin.jvm.JvmName

@JvmName("frequencyTimesAbhenry")
infix operator fun <FrequencyUnit : Frequency> ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>.times(
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, Abhenry>
) = inductance * this

@JvmName("frequencyTimesInductance")
infix operator fun <InductanceUnit : ElectricInductance, FrequencyUnit : Frequency> ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>.times(
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>
) = inductance * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.frequency

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.BeatsPerMinute
import com.splendo.kaluga.scientific.unit.Frequency
import com.splendo.kaluga.scientific.unit.Minute
import com.splendo.kaluga.scientific.unit.Second
import kotlin.jvm.JvmName

@JvmName("beatsPerMinuteTime")
fun ScientificValue<PhysicalQuantity.Frequency, BeatsPerMinute>.time() = Minute.time(this)

@JvmName("time")
fun <FrequencyUnit : Frequency> ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>.time() =
    Second.time(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.heatCapacity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.converter.temperature.deltaValue
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.HeatCapacity
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.Temperature
import kotlin.jvm.JvmName

@JvmName("heatCapacityFromEnergyAndTemperatureDefault")
fun <
    EnergyUnit : Energy,
    TemperatureUnit : Temperature,
    HeatCapacityUnit : HeatCapacity
    > HeatCapacityUnit.heatCapacity(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = heatCapacity(energy, temperature, ::DefaultScientificValue)

@JvmName("heatCapacityFromEnergyAndTemperature")
fun <
    EnergyUnit : Energy,
    TemperatureUnit : Temperature,
    HeatCapacityUnit : HeatCapacity,
    Value : ScientificValue<PhysicalQuantity.HeatCapacity, HeatCapacityUnit>
    > HeatCapacityUnit.heatCapacity(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>,
    factory: (Decimal, HeatCapacityUnit) -> Value
) = byDividing(energy, Kelvin.deltaValue(temperature), factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.heatCapacity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.HeatCapacity
import com.splendo.kaluga.scientific.unit.SpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("heatCapacityFromWeightAndSpecificHeatCapacityDefault")
fun <
    HeatCapacityUnit : HeatCapacity,
    WeightUnit : Weight,
    SpecificHeatCapacityUnit : SpecificHeatCapacity
    > HeatCapacityUnit.heatCapacity(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = heatCapacity(specificHeatCapacity, weight, ::DefaultScientificValue)

@JvmName("heatCapacityFromWeightAndSpecificHeatCapacity")
fun <
    HeatCapacityUnit : HeatCapacity,
    WeightUnit : Weight,
    SpecificHeatCapacityUnit : SpecificHeatCapacity,
    Value : ScientificValue<PhysicalQuantity.HeatCapacity, HeatCapacityUnit>
    > HeatCapacityUnit.heatCapacity(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    factory: (Decimal, HeatCapacityUnit) -> Value
) = byMultiplying(specificHeatCapacity, weight, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.heatCapacity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.energy.energy
import com.splendo.kaluga.scientific.unit.HeatCapacity
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialHeatCapacity
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialTemperature
import com.splendo.kaluga.scientific.unit.MetricHeatCapacity
import com.splendo.kaluga.scientific.unit.Temperature
import com.splendo.kaluga.scientific.unit.UKImperialHeatCapacity
import com.splendo.kaluga.scientific.unit.USCustomaryHeatCapacity
import com.splendo.kaluga.scientific.unit.USCustomaryTemperature
import kotlin.jvm.JvmName

@JvmName("metricAndUKImperialHeatCapacityTimesMetricAndUKImperialTemperature")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.HeatCapacity, MetricAndUKImperialHeatCapacity>.times(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (unit.energy).energy(this, temperature)

@JvmName("metricHeatCapacityTimesMetricAndUKImperialTemperature")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.HeatCapacity, MetricHeatCapacity>.times(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (unit.energy).energy(this, temperature)

@JvmName("ukImperialHeatCapacityTimesMetricAndUKImperialTemperature")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.HeatCapacity, UKImperialHeatCapacity>.times(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (unit.energy).energy(this, temperature)

@JvmName("usCustomaryHeatCapacityTimesUSCustomaryTemperature")
infix operator fun <TemperatureUnit : USCustomaryTemperature> ScientificValue<PhysicalQuantity.HeatCapacity, USCustomaryHeatCapacity>.times(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (unit.energy).energy(this, temperature)

@JvmName("heatCapacityTimesTemperature")
infix operator fun <HeatCapacityUnit : HeatCapacity, TemperatureUnit : Temperature> ScientificValue<PhysicalQuantity.HeatCapacity, HeatCapacityUnit>.times(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (unit.energy).energy(this, temperature)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.heatCapacity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.specificHeatCapacity.specificHeatCapacity
import com.splendo.kaluga.scientific.unit.HeatCapacity
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialHeatCapacity
import com.splendo.kaluga.scientific.unit.MetricHeatCapacity
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.UKImperialHeatCapacity
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryHeatCapacity
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricAndUKImperialHeatCapacityDivMetricWeight")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.HeatCapacity, MetricAndUKImperialHeatCapacity>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificHeatCapacity(this, weight)

@JvmName("metricAndUKImperialHeatCapacityDivImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.HeatCapacity, MetricAndUKImperialHeatCapacity>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificHeatCapacity(this, weight)

@JvmName("metricAndUKImperialHeatCapacityDivUKImperialWeight")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.HeatCapacity, MetricAndUKImperialHeatCapacity>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificHeatCapacity(this, weight)

@JvmName("metricHeatCapacityDivMetricWeight")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.HeatCapacity, MetricHeatCapacity>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificHeatCapacity(this, weight)

@JvmName("ukImperialHeatCapacityDivImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.HeatCapacity, UKImperialHeatCapacity>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificHeatCapacity(this, weight)

@JvmName("ukImperialHeatCapacityDivUKImperialWeight")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.HeatCapacity, UKImperialHeatCapacity>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificHeatCapacity(this, weight)

@JvmName("usCustomaryHeatCapacityDivImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.HeatCapacity, USCustomaryHeatCapacity>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificHeatCapacity(this, weight)

@JvmName("usCustomaryHeatCapacityDivUSCustomaryWeight")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.HeatCapacity, USCustomaryHeatCapacity>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificHeatCapacity(this, weight)

@JvmName("heatCapacityDivWeight")
infix operator fun <HeatCapacityUnit : HeatCapacity, WeightUnit : Weight> ScientificValue<PhysicalQuantity.HeatCapacity, HeatCapacityUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (Joule per Kelvin per Kilogram).specificHeatCapacity(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.heatCapacity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.weight.weight
import com.splendo.kaluga.scientific.unit.HeatCapacity
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialHeatCapacity
import com.splendo.kaluga.scientific.unit.MetricHeatCapacity
import com.splendo.kaluga.scientific.unit.MetricSpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.SpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.UKImperialHeatCapacity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.USCustomaryHeatCapacity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificHeatCapacity
import kotlin.jvm.JvmName

@JvmName("metricAndUKImperialHeatCapacityDivMetricSpecificHeatCapacity")
infix operator fun ScientificValue<PhysicalQuantity.HeatCapacity, MetricAndUKImperialHeatCapacity>.div(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, MetricSpecificHeatCapacity>
) = specificHeatCapacity.unit.perWeight.weight(this, specificHeatCapacity)

@JvmName("metricAndUKImperialHeatCapacityDivUKImperialSpecificHeatCapacity")
infix operator fun ScientificValue<PhysicalQuantity.HeatCapacity, MetricAndUKImperialHeatCapacity>.div(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, UKImperialSpecificHeatCapacity>
) = specificHeatCapacity.unit.perWeight.weight(this, specificHeatCapacity)

@JvmName("metricHeatCapacityDivMetricSpecificHeatCapacity")
infix operator fun ScientificValue<PhysicalQuantity.HeatCapacity, MetricHeatCapacity>.div(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, MetricSpecificHeatCapacity>
) = specificHeatCapacity.unit.perWeight.weight(this, specificHeatCapacity)

@JvmName("ukImperialHeatCapacityDivUKImperialSpecificHeatCapacity")
infix operator fun ScientificValue<PhysicalQuantity.HeatCapacity, UKImperialHeatCapacity>.div(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, UKImperialSpecificHeatCapacity>
) = specificHeatCapacity.unit.perWeight.weight(this, specificHeatCapacity)

@JvmName("usCustomaryHeatCapacityDivUSCustomarySpecificHeatCapacity")
infix operator fun ScientificValue<PhysicalQuantity.HeatCapacity, USCustomaryHeatCapacity>.div(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, USCustomarySpecificHeatCapacity>
) = specificHeatCapacity.unit.perWeight.weight(this, specificHeatCapacity)

@JvmName("heatCapacityDivSpecificHeatCapacity")
infix operator fun <HeatCapacityUnit : HeatCapacity, SpecificHeatCapacityUnit : SpecificHeatCapacity> ScientificValue<PhysicalQuantity.HeatCapacity, HeatCapacityUnit>.div(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>
) = Kilogram.weight(this, specificHeatCapacity)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.illuminance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.Luminance
import com.splendo.kaluga.scientific.unit.SolidAngle
import kotlin.jvm.JvmName

@JvmName("illuminanceFromLuminanceAndSolidAngleDefault")
fun <
    LuminanceUnit : Luminance,
    SolidAngleUnit : SolidAngle,
    IlluminanceUnit : Illuminance
    > IlluminanceUnit.illuminance(
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>,
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
) = illuminance(luminance, solidAngle, ::DefaultScientificValue)

@JvmName("illuminanceFromLuminanceAndSolidAngle")
fun <
    LuminanceUnit : Luminance,
    SolidAngleUnit : SolidAngle,
    IlluminanceUnit : Illuminance,
    Value : ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>
    > IlluminanceUnit.illuminance(
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>,
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>,
    factory: (Decimal, IlluminanceUnit) -> Value
) = byMultiplying(luminance, solidAngle, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.illuminance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.LuminousExposure
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("illuminanceFromLuminousExposureAndTimeDefault")
fun <
    LuminousExposureUnit : LuminousExposure,
    TimeUnit : Time,
    IlluminanceUnit : Illuminance
    > IlluminanceUnit.illuminance(
    luminousExposure: ScientificValue<PhysicalQuantity.LuminousExposure, LuminousExposureUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = illuminance(luminousExposure, time, ::DefaultScientificValue)

@JvmName("illuminanceFromLuminousExposureAndTime")
fun <
    LuminousExposureUnit : LuminousExposure,
    TimeUnit : Time,
    IlluminanceUnit : Illuminance,
    Value : ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>
    > IlluminanceUnit.illuminance(
    luminousExposure: ScientificValue<PhysicalQuantity.LuminousExposure, LuminousExposureUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, IlluminanceUnit) -> Value
) = byDividing(luminousExposure, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.illuminance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.LuminousFlux
import kotlin.jvm.JvmName

@JvmName("illuminanceFromLuminousFluxAndAreaDefault")
fun <
    FluxUnit : LuminousFlux,
    AreaUnit : Area,
    IlluminanceUnit : Illuminance
    > IlluminanceUnit.illuminance(
    flux: ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = illuminance(flux, area, ::DefaultScientificValue)

@JvmName("illuminanceFromLuminousFluxAndArea")
fun <
    FluxUnit : LuminousFlux,
    AreaUnit : Area,
    IlluminanceUnit : Illuminance,
    Value : ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>
    > IlluminanceUnit.illuminance(
    flux: ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, IlluminanceUnit) -> Value
) = byDividing(flux, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.illuminance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.luminance.luminance
import com.splendo.kaluga.scientific.unit.FootLambert
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.ImperialIlluminance
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Nit
import com.splendo.kaluga.scientific.unit.Phot
import com.splendo.kaluga.scientific.unit.SolidAngle
import com.splendo.kaluga.scientific.unit.Stilb
import kotlin.jvm.JvmName

@JvmName("photDivSolidAngle")
infix operator fun <SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.Illuminance, Phot>.div(
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
) = Stilb.luminance(this, solidAngle)

@JvmName("photMultipleDivSolidAngle")
infix operator fun <PhotUnit, SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.Illuminance, PhotUnit>.div(
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
) where PhotUnit : Illuminance, PhotUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Phot> =
    Stilb.luminance(this, solidAngle)

@JvmName("imperialIlluminanceDivSolidAngle")
infix operator fun <IlluminanceUnit : ImperialIlluminance, SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>.div(
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
) = FootLambert.luminance(this, solidAngle)

@JvmName("illuminanceDivSolidAngle")
infix operator fun <IlluminanceUnit : Illuminance, SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>.div(
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
) = Nit.luminance(this, solidAngle)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.illuminance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.luminousExposure.luminousExposure
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.ImperialIlluminance
import com.splendo.kaluga.scientific.unit.Lux
import com.splendo.kaluga.scientific.unit.MetricIlluminance
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.x
import kotlin.jvm.JvmName

@JvmName("metricIlluminanceTimesTime")
infix operator fun <IlluminanceUnit : MetricIlluminance, TimeUnit : Time> ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit x time.unit).luminousExposure(this, time)

@JvmName("imperialIlluminanceTimesTime")
infix operator fun <IlluminanceUnit : ImperialIlluminance, TimeUnit : Time> ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit x time.unit).luminousExposure(this, time)

@JvmName("illuminanceTimesTime")
infix operator fun <IlluminanceUnit : Illuminance, TimeUnit : Time> ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (Lux x time.unit).luminousExposure(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.illuminance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.luminousFlux.flux
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.Lumen
import kotlin.jvm.JvmName

@JvmName("illuminanceTimesArea")
infix operator fun <IlluminanceUnit : Illuminance, AreaUnit : Area> ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Lumen.flux(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.illuminance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.solidAngle.solidAngle
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.Luminance
import com.splendo.kaluga.scientific.unit.Steradian
import kotlin.jvm.JvmName

@JvmName("illuminanceDivLuminance")
infix operator fun <IlluminanceUnit : Illuminance, LuminanceUnit : Luminance> ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>.div(
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>
) = Steradian.solidAngle(this, luminance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.ionizingRadiationAbsorbedDose

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.IonizingRadiationAbsorbedDose
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("absorbedDoseFromEnergyAndWeightDefault")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    AbsorbedDoseUnit : IonizingRadiationAbsorbedDose
    > AbsorbedDoseUnit.absorbedDose(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = absorbedDose(energy, weight, ::DefaultScientificValue)

@JvmName("absorbedDoseFromEnergyAndWeight")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    AbsorbedDoseUnit : IonizingRadiationAbsorbedDose,
    Value : ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>
    > AbsorbedDoseUnit.absorbedDose(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    factory: (Decimal, AbsorbedDoseUnit) -> Value
) = byDividing(energy, weight, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.ionizingRadiationAbsorbedDose

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.convert
import com.splendo.kaluga.scientific.unit.Gray
import com.splendo.kaluga.scientific.unit.IonizingRadiationAbsorbedDose
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("specificEnergyFromAbsorbedDoseDefault")
fun <
    AbsorbedDoseUnit : IonizingRadiationAbsorbedDose,
    SpecificEnergyUnit : SpecificEnergy
    > AbsorbedDoseUnit.absorbedDose(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>
) = absorbedDose(specificEnergy, ::DefaultScientificValue)

@JvmName("specificEnergyFromAbsorbedDose")
fun <
    AbsorbedDoseUnit : IonizingRadiationAbsorbedDose,
    SpecificEnergyUnit : SpecificEnergy,
    Value : ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>
    > AbsorbedDoseUnit.absorbedDose(
    absorbedDose: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    factory: (Decimal, AbsorbedDoseUnit) -> Value
) = DefaultScientificValue(absorbedDose.convert(Joule per Kilogram).value, Gray).convert(
    this,
    factory
)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.ionizingRadiationAbsorbedDose

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.energy.energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.FootPoundForce
import com.splendo.kaluga.scientific.unit.Gram
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.IonizingRadiationAbsorbedDose
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Rad
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("radTimesGram")
infix operator fun ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, Gram>
) = Erg.energy(this, weight)

@JvmName("radMultipleTimesGram")
infix operator fun <AbsorbedDoseUnit> ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, Gram>
) where AbsorbedDoseUnit : IonizingRadiationAbsorbedDose, AbsorbedDoseUnit : MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad> =
    Erg.energy(this, weight)

@JvmName("absorbedDoseTimesImperialWeight")
infix operator fun <AbsorbedDoseUnit : IonizingRadiationAbsorbedDose, WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = FootPoundForce.energy(this, weight)

@JvmName("absorbedDoseTimesUKImperialWeight")
infix operator fun <AbsorbedDoseUnit : IonizingRadiationAbsorbedDose, WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = FootPoundForce.energy(this, weight)

@JvmName("absorbedDoseTimesUSCustomaryWeight")
infix operator fun <AbsorbedDoseUnit : IonizingRadiationAbsorbedDose, WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = FootPoundForce.energy(this, weight)

@JvmName("absorbedDoseTimesWeight")
infix operator fun <AbsorbedDoseUnit : IonizingRadiationAbsorbedDose, WeightUnit : Weight> ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = Joule.energy(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.ionizingRadiationAbsorbedDose

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.specificEnergy.specificEnergy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.Gram
import com.splendo.kaluga.scientific.unit.IonizingRadiationAbsorbedDose
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Rad
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("specificEnergyFromRad")
fun ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad>.asSpecificEnergy() =
    (Erg per Gram).specificEnergy(this)

@JvmName("specificEnergyFromRadMultiple")
fun <RadUnit> ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, RadUnit>.asSpecificEnergy() where RadUnit : IonizingRadiationAbsorbedDose, RadUnit : MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad> =
    (Erg per Gram).specificEnergy(this)

@JvmName("specificEnergyFromAbsorbedDose")
fun <AbsorbedDoseUnit : IonizingRadiationAbsorbedDose> ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>.asSpecificEnergy() =
    (Joule per Kilogram).specificEnergy(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.ionizingRadiationEquivalentDose

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.IonizingRadiationEquivalentDose
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("equivalentDoseFromEnergyAndWeightDefault")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    EquivalentDoseUnit : IonizingRadiationEquivalentDose
    > EquivalentDoseUnit.equivalentDose(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = equivalentDose(energy, weight, ::DefaultScientificValue)

@JvmName("equivalentDoseFromEnergyAndWeight")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    EquivalentDoseUnit : IonizingRadiationEquivalentDose,
    Value : ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>
    > EquivalentDoseUnit.equivalentDose(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    factory: (Decimal, EquivalentDoseUnit) -> Value
) = byDividing(energy, weight, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.ionizingRadiationEquivalentDose

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.convert
import com.splendo.kaluga.scientific.unit.IonizingRadiationEquivalentDose
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.Sievert
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("specificEnergyFromEquivalentDoseDefault")
fun <
    EquivalentDoseUnit : IonizingRadiationEquivalentDose,
    SpecificEnergyUnit : SpecificEnergy
    > EquivalentDoseUnit.equivalentDose(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>
) = equivalentDose(specificEnergy, ::DefaultScientificValue)

@JvmName("specificEnergyFromEquivalentDose")
fun <
    EquivalentDoseUnit : IonizingRadiationEquivalentDose,
    SpecificEnergyUnit : SpecificEnergy,
    Value : ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>
    > EquivalentDoseUnit.equivalentDose(
    equivalentDose: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    factory: (Decimal, EquivalentDoseUnit) -> Value
) = DefaultScientificValue(equivalentDose.convert(Joule per Kilogram).value, Sievert).convert(
    this,
    factory
)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.ionizingRadiationEquivalentDose

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.energy.energy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.FootPoundForce
import com.splendo.kaluga.scientific.unit.Gram
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.IonizingRadiationEquivalentDose
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.RoentgenEquivalentMan
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("roentgenEquivalentManTimesGram")
infix operator fun ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, Gram>
) = Erg.energy(this, weight)

@JvmName("roentgenEquivalentManMultipleTimesGram")
infix operator fun <EquivalentDoseUnit> ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, Gram>
) where EquivalentDoseUnit : IonizingRadiationEquivalentDose, EquivalentDoseUnit : MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan> =
    Erg.energy(this, weight)

@JvmName("equivalentDoseTimesImperialWeight")
infix operator fun <EquivalentDoseUnit : IonizingRadiationEquivalentDose, WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = FootPoundForce.energy(this, weight)

@JvmName("equivalentDoseTimesUKImperialWeight")
infix operator fun <EquivalentDoseUnit : IonizingRadiationEquivalentDose, WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = FootPoundForce.energy(this, weight)

@JvmName("equivalentDoseTimesUSCustomaryWeight")
infix operator fun <EquivalentDoseUnit : IonizingRadiationEquivalentDose, WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = FootPoundForce.energy(this, weight)

@JvmName("equivalentDoseTimesWeight")
infix operator fun <EquivalentDoseUnit : IonizingRadiationEquivalentDose, WeightUnit : Weight> ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = Joule.energy(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.ionizingRadiationEquivalentDose

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.specificEnergy.specificEnergy
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.Gram
import com.splendo.kaluga.scientific.unit.IonizingRadiationEquivalentDose
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.RoentgenEquivalentMan
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("specificEnergyFromRoentgewnEquivalentMan")
fun ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan>.asSpecificEnergy() =
    (Erg per Gram).specificEnergy(this)

@JvmName("specificEnergyFromRoentgenEquivalentManMultiple")
fun <RoentgenEquivalentManUnit> ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentManUnit>.asSpecificEnergy() where RoentgenEquivalentManUnit : IonizingRadiationEquivalentDose, RoentgenEquivalentManUnit : MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan> =
    (Erg per Gram).specificEnergy(this)

@JvmName("specificEnergyFromEquivalentDose")
fun <EquivalentDoseUnit : IonizingRadiationEquivalentDose> ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>.asSpecificEnergy() =
    (Joule per Kilogram).specificEnergy(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.jolt

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.Jolt
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("joltFromAccelerationAndTimeDefault")
fun <
    AccelerationUnit : Acceleration,
    TimeUnit : Time,
    JoltUnit : Jolt
    > JoltUnit.jolt(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = jolt(acceleration, time, ::DefaultScientificValue)

@JvmName("joltFromAccelerationAndTime")
fun <
    AccelerationUnit : Acceleration,
    TimeUnit : Time,
    JoltUnit : Jolt,
    Value : ScientificValue<PhysicalQuantity.Jolt, JoltUnit>
    > JoltUnit.jolt(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, JoltUnit) -> Value
) = byDividing(acceleration, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.jolt

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Jolt
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.Yank
import kotlin.jvm.JvmName

@JvmName("joltFromYankAndMassDefault")
fun <
    WeightUnit : Weight,
    JoltUnit : Jolt,
    YankUnit : Yank
    > JoltUnit.jolt(
    yank: ScientificValue<PhysicalQuantity.Yank, YankUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = jolt(yank, weight, ::DefaultScientificValue)

@JvmName("joltFromYankAndMass")
fun <
    WeightUnit : Weight,
    JoltUnit : Jolt,
    YankUnit : Yank,
    Value : ScientificValue<PhysicalQuantity.Jolt, JoltUnit>
    > JoltUnit.jolt(
    yank: ScientificValue<PhysicalQuantity.Yank, YankUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    factory: (Decimal, JoltUnit) -> Value
) = byDividing(yank, weight, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.jolt

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.acceleration.acceleration
import com.splendo.kaluga.scientific.unit.ImperialJolt
import com.splendo.kaluga.scientific.unit.Jolt
import com.splendo.kaluga.scientific.unit.MetricJolt
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("metricJoltTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Jolt, MetricJolt>.times(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    (unit.acceleration).acceleration(this, time)

@JvmName("imperialJoltTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Jolt, ImperialJolt>.times(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    (unit.acceleration).acceleration(this, time)

@JvmName("joltTimesTime")
infix operator fun <JoltUnit : Jolt, TimeUnit : Time> ScientificValue<PhysicalQuantity.Jolt, JoltUnit>.times(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    (unit.acceleration).acceleration(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.jolt

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.weight.times
import com.splendo.kaluga.scientific.unit.ImperialJolt
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.Jolt
import com.splendo.kaluga.scientific.unit.MetricJolt
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("metricJoltTimesMetricWeight")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Jolt, MetricJolt>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = weight * this

@JvmName("imperialJoltTimesImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Jolt, ImperialJolt>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = weight * this

@JvmName("imperialJoltTimesUKImperialWeight")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Jolt, ImperialJolt>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = weight * this

@JvmName("imperialJoltTimesUSCustomaryWeight")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Jolt, ImperialJolt>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = weight * this

@JvmName("joltTimesWeight")
infix operator fun <WeightUnit : Weight, JoltUnit : Jolt> ScientificValue<PhysicalQuantity.Jolt, JoltUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = weight * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Length
import kotlin.jvm.JvmName

@JvmName("widthFromAreaAndLengthDefault")
fun <
    LengthUnit : Length,
    WidthUnit : Length,
    AreaUnit : Area
    > WidthUnit.width(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = width(area, length, ::DefaultScientificValue)

@JvmName("widthFromAreaAndLength")
fun <
    LengthUnit : Length,
    WidthUnit : Length,
    AreaUnit : Area,
    Value : ScientificValue<PhysicalQuantity.Length, WidthUnit>
    > WidthUnit.width(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    factory: (Decimal, WidthUnit) -> Value
) = byDividing(area, length, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.Length
import kotlin.jvm.JvmName

@JvmName("lengthFromAreaDensityAndDensityDefault")
fun <
    DensityUnit : Density,
    LengthUnit : Length,
    AreaDensityUnit : AreaDensity
    > LengthUnit.length(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>,
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = length(areaDensity, density, ::DefaultScientificValue)

@JvmName("lengthFromAreaDensityAndDensity")
fun <
    DensityUnit : Density,
    LengthUnit : Length,
    AreaDensityUnit : AreaDensity,
    Value : ScientificValue<PhysicalQuantity.Length, LengthUnit>
    > LengthUnit.length(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>,
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    factory: (Decimal, LengthUnit) -> Value
) = byDividing(areaDensity, density, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("lengthFromAreaDensityAndSpecificVolumeDefault")
fun <
    SpecificVolumeUnit : SpecificVolume,
    LengthUnit : Length,
    AreaDensityUnit : AreaDensity
    > LengthUnit.length(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>
) = length(specificVolume, areaDensity, ::DefaultScientificValue)

@JvmName("lengthFromAreaDensityAndSpecificVolume")
fun <
    SpecificVolumeUnit : SpecificVolume,
    LengthUnit : Length,
    AreaDensityUnit : AreaDensity,
    Value : ScientificValue<PhysicalQuantity.Length, LengthUnit>
    > LengthUnit.length(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>,
    factory: (Decimal, LengthUnit) -> Value
) = byMultiplying(specificVolume, areaDensity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Length
import kotlin.jvm.JvmName

@JvmName("lengthFromEnergyAndForceDefault")
fun <
    EnergyUnit : Energy,
    ForceUnit : Force,
    LengthUnit : Length
    > LengthUnit.distance(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = distance(energy, force, ::DefaultScientificValue)

@JvmName("lengthFromEnergyAndForce")
fun <
    EnergyUnit : Energy,
    ForceUnit : Force,
    LengthUnit : Length,
    Value : ScientificValue<PhysicalQuantity.Length, LengthUnit>
    > LengthUnit.distance(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    factory: (Decimal, LengthUnit) -> Value
) = byDividing(energy, force, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.SurfaceTension
import kotlin.jvm.JvmName

@JvmName("lengthFromForceAndSurfaceTensionDefault")
fun <
    ForceUnit : Force,
    LengthUnit : Length,
    SurfaceTensionUnit : SurfaceTension
    > LengthUnit.length(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>
) = length(force, surfaceTension, ::DefaultScientificValue)

@JvmName("lengthFromForceAndSurfaceTension")
fun <
    ForceUnit : Force,
    LengthUnit : Length,
    SurfaceTensionUnit : SurfaceTension,
    Value : ScientificValue<PhysicalQuantity.Length, LengthUnit>
    > LengthUnit.length(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>,
    factory: (Decimal, LengthUnit) -> Value
) = byDividing(force, surfaceTension, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import kotlin.jvm.JvmName

@JvmName("lengthFromLinearMassDensityAndDensityDefault")
fun <
    AreaDensityUnit : AreaDensity,
    LengthUnit : Length,
    LinearMassDensityUnit : LinearMassDensity
    > LengthUnit.length(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>,
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>
) = length(linearMassDensity, areaDensity, ::DefaultScientificValue)

@JvmName("lengthFromLinearMassDensityAndDensity")
fun <
    AreaDensityUnit : AreaDensity,
    LengthUnit : Length,
    LinearMassDensityUnit : LinearMassDensity,
    Value : ScientificValue<PhysicalQuantity.Length, LengthUnit>
    > LengthUnit.length(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>,
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>,
    factory: (Decimal, LengthUnit) -> Value
) = byDividing(linearMassDensity, areaDensity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("distanceFromSpeedAndTimeDefault")
fun <
    LengthUnit : Length,
    TimeUnit : Time,
    SpeedUnit : Speed
    > LengthUnit.distance(
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = distance(speed, time, ::DefaultScientificValue)

@JvmName("distanceFromSpeedAndTime")
fun <
    LengthUnit : Length,
    TimeUnit : Time,
    SpeedUnit : Speed,
    Value : ScientificValue<PhysicalQuantity.Length, LengthUnit>
    > LengthUnit.distance(
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, LengthUnit) -> Value
) = byMultiplying(speed, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.converter.area.area
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("heightFromVolumeAndAreaDefault")
fun <
    HeightUnit : Length,
    AreaUnit : Area,
    VolumeUnit : Volume
    > HeightUnit.height(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = height(volume, area, ::DefaultScientificValue)

@JvmName("heightFromVolumeAndArea")
fun <
    HeightUnit : Length,
    AreaUnit : Area,
    VolumeUnit : Volume,
    Value : ScientificValue<PhysicalQuantity.Length, HeightUnit>
    > HeightUnit.height(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, HeightUnit) -> Value
) = byDividing(volume, area, factory)

@JvmName("heightFromVolumeLengthAndWidthDefault")
fun <
    HeightUnit : Length,
    LengthUnit : Length,
    WidthUnit : Length,
    VolumeUnit : Volume
    > HeightUnit.height(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    width: ScientificValue<PhysicalQuantity.Length, WidthUnit>,
) = height(volume, length, width, ::DefaultScientificValue)

@JvmName("heightFromVolumeLengthAndWidth")
fun <
    HeightUnit : Length,
    LengthUnit : Length,
    WidthUnit : Length,
    VolumeUnit : Volume,
    Value : ScientificValue<PhysicalQuantity.Length, HeightUnit>
    > HeightUnit.height(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    width: ScientificValue<PhysicalQuantity.Length, WidthUnit>,
    factory: (Decimal, HeightUnit) -> Value
) = height(volume, SquareMeter.area(length, width), factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("lengthFromWeightAndLinearMassDensityDefault")
fun <
    WeightUnit : Weight,
    LengthUnit : Length,
    LinearMassDensityUnit : LinearMassDensity
    > LengthUnit.length(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>
) = length(weight, linearMassDensity, ::DefaultScientificValue)

@JvmName("lengthFromWeightAndLinearMassDensity")
fun <
    WeightUnit : Weight,
    LengthUnit : Length,
    LinearMassDensityUnit : LinearMassDensity,
    Value : ScientificValue<PhysicalQuantity.Length, LengthUnit>
    > LengthUnit.length(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>,
    factory: (Decimal, LengthUnit) -> Value
) = byDividing(weight, linearMassDensity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.area
import com.splendo.kaluga.scientific.unit.Centimeter
import com.splendo.kaluga.scientific.unit.Decameter
import com.splendo.kaluga.scientific.unit.Decimeter
import com.splendo.kaluga.scientific.unit.Foot
import com.splendo.kaluga.scientific.unit.Gigameter
import com.splendo.kaluga.scientific.unit.Hectometer
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.Inch
import com.splendo.kaluga.scientific.unit.Kilometer
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.Megameter
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.Micrometer
import com.splendo.kaluga.scientific.unit.Mile
import com.splendo.kaluga.scientific.unit.Millimeter
import com.splendo.kaluga.scientific.unit.Nanometer
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.SquareDecameter
import com.splendo.kaluga.scientific.unit.SquareDecimeter
import com.splendo.kaluga.scientific.unit.SquareFoot
import com.splendo.kaluga.scientific.unit.SquareGigameter
import com.splendo.kaluga.scientific.unit.SquareHectometer
import com.splendo.kaluga.scientific.unit.SquareInch
import com.splendo.kaluga.scientific.unit.SquareKilometer
import com.splendo.kaluga.scientific.unit.SquareMegameter
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.SquareMicrometer
import com.splendo.kaluga.scientific.unit.SquareMile
import com.splendo.kaluga.scientific.unit.SquareMillimeter
import com.splendo.kaluga.scientific.unit.SquareNanometer
import com.splendo.kaluga.scientific.unit.SquareYard
import com.splendo.kaluga.scientific.unit.Yard
import kotlin.jvm.JvmName

@JvmName("meterTimesMeter")
infix operator fun ScientificValue<PhysicalQuantity.Length, Meter>.times(width: ScientificValue<PhysicalQuantity.Length, Meter>) =
    SquareMeter.area(this, width)

@JvmName("nanometerTimesNanometer")
infix operator fun ScientificValue<PhysicalQuantity.Length, Nanometer>.times(width: ScientificValue<PhysicalQuantity.Length, Nanometer>) =
    SquareNanometer.area(this, width)

@JvmName("micrometerTimesMicrometer")
infix operator fun ScientificValue<PhysicalQuantity.Length, Micrometer>.times(width: ScientificValue<PhysicalQuantity.Length, Micrometer>) =
    SquareMicrometer.area(this, width)

@JvmName("millimeterTimesMillimeter")
infix operator fun ScientificValue<PhysicalQuantity.Length, Millimeter>.times(width: ScientificValue<PhysicalQuantity.Length, Millimeter>) =
    SquareMillimeter.area(this, width)

@JvmName("centimeterTimesCentieter")
infix operator fun ScientificValue<PhysicalQuantity.Length, Centimeter>.times(width: ScientificValue<PhysicalQuantity.Length, Centimeter>) =
    SquareCentimeter.area(this, width)

@JvmName("decimeterTimesDecimeter")
infix operator fun ScientificValue<PhysicalQuantity.Length, Decimeter>.times(width: ScientificValue<PhysicalQuantity.Length, Decimeter>) =
    SquareDecimeter.area(this, width)

@JvmName("decameterTimesDecameter")
infix operator fun ScientificValue<PhysicalQuantity.Length, Decameter>.times(width: ScientificValue<PhysicalQuantity.Length, Decameter>) =
    SquareDecameter.area(this, width)

@JvmName("hectometerTimesHectometer")
infix operator fun ScientificValue<PhysicalQuantity.Length, Hectometer>.times(width: ScientificValue<PhysicalQuantity.Length, Hectometer>) =
    SquareHectometer.area(this, width)

@JvmName("kilometerTimesKilometer")
infix operator fun ScientificValue<PhysicalQuantity.Length, Kilometer>.times(width: ScientificValue<PhysicalQuantity.Length, Kilometer>) =
    SquareKilometer.area(this, width)

@JvmName("megameterTimesMegameter")
infix operator fun ScientificValue<PhysicalQuantity.Length, Megameter>.times(width: ScientificValue<PhysicalQuantity.Length, Megameter>) =
    SquareMegameter.area(this, width)

@JvmName("gigameterTimesGigameter")
infix operator fun ScientificValue<PhysicalQuantity.Length, Gigameter>.times(width: ScientificValue<PhysicalQuantity.Length, Gigameter>) =
    SquareGigameter.area(this, width)

@JvmName("metricLengthTimesMetricLength")
infix operator fun <LengthUnit : MetricLength, WidthUnit : MetricLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    width: ScientificValue<PhysicalQuantity.Length, WidthUnit>
) = SquareMeter.area(this, width)

@JvmName("inchTimesInch")
infix operator fun ScientificValue<PhysicalQuantity.Length, Inch>.times(width: ScientificValue<PhysicalQuantity.Length, Inch>) =
    SquareInch.area(this, width)

@JvmName("footTimesFoot")
infix operator fun ScientificValue<PhysicalQuantity.Length, Foot>.times(width: ScientificValue<PhysicalQuantity.Length, Foot>) =
    SquareFoot.area(this, width)

@JvmName("yardTimesYard")
infix operator fun ScientificValue<PhysicalQuantity.Length, Yard>.times(width: ScientificValue<PhysicalQuantity.Length, Yard>) =
    SquareYard.area(this, width)

@JvmName("mileTimesMile")
infix operator fun ScientificValue<PhysicalQuantity.Length, Mile>.times(width: ScientificValue<PhysicalQuantity.Length, Mile>) =
    SquareMile.area(this, width)

@JvmName("imperialLengthTimesImperialLength")
infix operator fun <LengthUnit : ImperialLength, WidthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    width: ScientificValue<PhysicalQuantity.Length, WidthUnit>
) = SquareFoot.area(this, width)

@JvmName("lengthTimesLength")
infix operator fun <LengthUnit : Length, WidthUnit : Length> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    width: ScientificValue<PhysicalQuantity.Length, WidthUnit>
) = SquareMeter.area(this, width)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.areaDensity.areaDensity
import com.splendo.kaluga.scientific.converter.density.times
import com.splendo.kaluga.scientific.converter.volume.div
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricLengthTimesMetricDensity")
infix operator fun <LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, MetricDensity>
) = density * this

@JvmName("imperialLengthTimesImperialDensity")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>
) = density * this

@JvmName("imperialLengthTimesUKImperialDensity")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, UKImperialDensity>
) = density * this

@JvmName("imperialLengthTimesUSCustomaryDensity")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>
) = density * this

@JvmName("lengthTimesDensity")
infix operator fun <DensityUnit : Density, LengthUnit : Length> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = density * this

@JvmName("metricLengthDivMetricSpecificVolume")
infix operator fun <LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>
) = (specificVolume.unit.per per (1(specificVolume.unit.volume) / 1(unit)).unit).areaDensity(
    this,
    specificVolume
)

@JvmName("imperialLengthDivImperialSpecificVolume")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = (specificVolume.unit.per per (1(specificVolume.unit.volume) / 1(unit)).unit).areaDensity(
    this,
    specificVolume
)

@JvmName("imperialLengthDivUKImperialSpecificVolume")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>
) = (specificVolume.unit.per per (1(specificVolume.unit.volume) / 1(unit)).unit).areaDensity(
    this,
    specificVolume
)

@JvmName("imperialLengthDivUSCustomarySpecificVolume")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>
) = (specificVolume.unit.per per (1(specificVolume.unit.volume) / 1(unit)).unit).areaDensity(
    this,
    specificVolume
)

@JvmName("lengthDivSpecificVolume")
infix operator fun <SpecificVolumeUnit : SpecificVolume, LengthUnit : Length> ScientificValue<PhysicalQuantity.Length, LengthUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = (Kilogram per SquareMeter).areaDensity(this, specificVolume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.force.times
import com.splendo.kaluga.scientific.unit.Centimeter
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.ImperialForce
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.Inch
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricForce
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.OunceForce
import com.splendo.kaluga.scientific.unit.PoundForce
import com.splendo.kaluga.scientific.unit.Poundal
import com.splendo.kaluga.scientific.unit.UKImperialForce
import com.splendo.kaluga.scientific.unit.USCustomaryForce
import kotlin.jvm.JvmName

@JvmName("centimeterTimesDyne")
infix operator fun ScientificValue<PhysicalQuantity.Length, Centimeter>.times(force: ScientificValue<PhysicalQuantity.Force, Dyne>) =
    force * this

@JvmName("centimeterTimesDyneMultiple")
infix operator fun <DyneUnit> ScientificValue<PhysicalQuantity.Length, Centimeter>.times(force: ScientificValue<PhysicalQuantity.Force, DyneUnit>) where DyneUnit : Force, DyneUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> =
    force * this

@JvmName("metricLengthTimesMetricForce")
infix operator fun <ForceUnit : MetricForce, LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = force * this

@JvmName("imperialLengthTimesPoundal")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    force: ScientificValue<PhysicalQuantity.Force, Poundal>
) = force * this

@JvmName("inchTimesPoundForce")
infix operator fun ScientificValue<PhysicalQuantity.Length, Inch>.times(force: ScientificValue<PhysicalQuantity.Force, PoundForce>) =
    force * this

@JvmName("inchTimesOunceForce")
infix operator fun ScientificValue<PhysicalQuantity.Length, Inch>.times(force: ScientificValue<PhysicalQuantity.Force, OunceForce>) =
    force * this

@JvmName("imperialLengthTimesPoundForce")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    force: ScientificValue<PhysicalQuantity.Force, PoundForce>
) = force * this

@JvmName("imperialLengthTimesImperialForce")
infix operator fun <ForceUnit : ImperialForce, LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = force * this

@JvmName("imperialLengthTimesUKImperialForce")
infix operator fun <ForceUnit : UKImperialForce, LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = force * this

@JvmName("imperialLengthTimesUSCustomaryForce")
infix operator fun <ForceUnit : USCustomaryForce, LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = force * this

@JvmName("lengthTimesForce")
infix operator fun <ForceUnit : Force, LengthUnit : Length> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = force * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.surfaceTension.times
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.ImperialSurfaceTension
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.MetricSurfaceTension
import com.splendo.kaluga.scientific.unit.SurfaceTension
import com.splendo.kaluga.scientific.unit.UKImperialSurfaceTension
import com.splendo.kaluga.scientific.unit.USCustomarySurfaceTension
import kotlin.jvm.JvmName

@JvmName("metricLengthTimesMetricSurfaceTension")
infix operator fun <LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, MetricSurfaceTension>
) = surfaceTension * this

@JvmName("imperialLengthTimesImperialSurfaceTension")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, ImperialSurfaceTension>
) = surfaceTension * this

@JvmName("imperialLengthTimesUKImperialSurfaceTension")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, UKImperialSurfaceTension>
) = surfaceTension * this

@JvmName("imperialLengthTimesUSCustomarySurfaceTension")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, USCustomarySurfaceTension>
) = surfaceTension * this

@JvmName("lengthTimesSurfaceTension")
infix operator fun <SurfaceTensionUnit : SurfaceTension, LengthUnit : Length> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    surfaceTension: ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>
) = surfaceTension * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.areaDensity.times
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.ImperialAreaDensity
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.MetricAreaDensity
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.UKImperialAreaDensity
import com.splendo.kaluga.scientific.unit.USCustomaryAreaDensity
import kotlin.jvm.JvmName

@JvmName("metricLengthTimesMetricAreaDensity")
infix operator fun <LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, MetricAreaDensity>
) = areaDensity * this

@JvmName("imperialLengthTimesImperialAreaDensity")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>
) = areaDensity * this

@JvmName("imperialLengthTimesUKImperialAreaDensity")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>
) = areaDensity * this

@JvmName("imperialLengthTimesUSCustomaryAreaDensity")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>
) = areaDensity * this

@JvmName("lengthTimesAreaDensity")
infix operator fun <AreaDensityUnit : AreaDensity, LengthUnit : Length> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>
) = areaDensity * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.times
import com.splendo.kaluga.scientific.converter.specificVolume.specificVolume
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialAreaDensity
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.MetricAreaDensity
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.UKImperialAreaDensity
import com.splendo.kaluga.scientific.unit.USCustomaryAreaDensity
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricLengthDivMetricAreaDensity")
infix operator fun <LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, MetricAreaDensity>
) = ((1(areaDensity.unit.per) * 1(unit)).unit per areaDensity.unit.weight).specificVolume(
    this,
    areaDensity
)

@JvmName("imperialLengthDivImperialAreaDensity")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>
) = ((1(areaDensity.unit.per) * 1(unit)).unit per areaDensity.unit.weight).specificVolume(
    this,
    areaDensity
)

@JvmName("imperialLengthDivUKImperialAreaDensity")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>
) = ((1(areaDensity.unit.per) * 1(unit)).unit per areaDensity.unit.weight).specificVolume(
    this,
    areaDensity
)

@JvmName("imperialLengthDivUSCustomaryAreaDensity")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>
) = ((1(areaDensity.unit.per) * 1(unit)).unit per areaDensity.unit.weight).specificVolume(
    this,
    areaDensity
)

@JvmName("lengthDivAreaDensity")
infix operator fun <AreaDensityUnit : AreaDensity, LengthUnit : Length> ScientificValue<PhysicalQuantity.Length, LengthUnit>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>
) = (CubicMeter per Kilogram).specificVolume(this, areaDensity)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.speed.speed
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricLengthDivTime")
infix operator fun <LengthUnit : MetricLength, TimeUnit : Time> ScientificValue<PhysicalQuantity.Length, LengthUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).speed(this, time)

@JvmName("imperialLengthDivTime")
infix operator fun <LengthUnit : ImperialLength, TimeUnit : Time> ScientificValue<PhysicalQuantity.Length, LengthUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).speed(this, time)

@JvmName("lengthDivTime")
infix operator fun <LengthUnit : Length, TimeUnit : Time> ScientificValue<PhysicalQuantity.Length, LengthUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (Meter per Second).speed(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.Speed
import kotlin.jvm.JvmName

@JvmName("lengthDivSpeed")
infix operator fun <LengthUnit : Length, SpeedUnit : Speed> ScientificValue<PhysicalQuantity.Length, LengthUnit>.div(
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>
) = speed.unit.per.time(this, speed)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.times
import com.splendo.kaluga.scientific.unit.Acre
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Centimeter
import com.splendo.kaluga.scientific.unit.Decameter
import com.splendo.kaluga.scientific.unit.Decimeter
import com.splendo.kaluga.scientific.unit.Foot
import com.splendo.kaluga.scientific.unit.Gigameter
import com.splendo.kaluga.scientific.unit.Hectometer
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.Inch
import com.splendo.kaluga.scientific.unit.Kilometer
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.Megameter
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.Micrometer
import com.splendo.kaluga.scientific.unit.Mile
import com.splendo.kaluga.scientific.unit.Millimeter
import com.splendo.kaluga.scientific.unit.Nanometer
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.SquareDecameter
import com.splendo.kaluga.scientific.unit.SquareDecimeter
import com.splendo.kaluga.scientific.unit.SquareFoot
import com.splendo.kaluga.scientific.unit.SquareGigameter
import com.splendo.kaluga.scientific.unit.SquareHectometer
import com.splendo.kaluga.scientific.unit.SquareInch
import com.splendo.kaluga.scientific.unit.SquareKilometer
import com.splendo.kaluga.scientific.unit.SquareMegameter
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.SquareMicrometer
import com.splendo.kaluga.scientific.unit.SquareMile
import com.splendo.kaluga.scientific.unit.SquareMillimeter
import com.splendo.kaluga.scientific.unit.SquareNanometer
import com.splendo.kaluga.scientific.unit.SquareYard
import com.splendo.kaluga.scientific.unit.Yard
import kotlin.jvm.JvmName

@JvmName("meterTimesSquareMeter")
infix operator fun ScientificValue<PhysicalQuantity.Length, Meter>.times(area: ScientificValue<PhysicalQuantity.Area, SquareMeter>) =
    area * this

@JvmName("nanometerTimesSquareNanometer")
infix operator fun ScientificValue<PhysicalQuantity.Length, Nanometer>.times(area: ScientificValue<PhysicalQuantity.Area, SquareNanometer>) =
    area * this

@JvmName("micrometerTimesSquareMicrometer")
infix operator fun ScientificValue<PhysicalQuantity.Length, Micrometer>.times(area: ScientificValue<PhysicalQuantity.Area, SquareMicrometer>) =
    area * this

@JvmName("millimeterTimesSquareMillimeter")
infix operator fun ScientificValue<PhysicalQuantity.Length, Millimeter>.times(area: ScientificValue<PhysicalQuantity.Area, SquareMillimeter>) =
    area * this

@JvmName("centimeterTimesSquareCentimeter")
infix operator fun ScientificValue<PhysicalQuantity.Length, Centimeter>.times(area: ScientificValue<PhysicalQuantity.Area, SquareCentimeter>) =
    area * this

@JvmName("decimeterTimesSquareDecimeter")
infix operator fun ScientificValue<PhysicalQuantity.Length, Decimeter>.times(area: ScientificValue<PhysicalQuantity.Area, SquareDecimeter>) =
    area * this

@JvmName("decameterTimesSquareDecameter")
infix operator fun ScientificValue<PhysicalQuantity.Length, Decameter>.times(area: ScientificValue<PhysicalQuantity.Area, SquareDecameter>) =
    area * this

@JvmName("hectometerTimesSquarehectometer")
infix operator fun ScientificValue<PhysicalQuantity.Length, Hectometer>.times(area: ScientificValue<PhysicalQuantity.Area, SquareHectometer>) =
    area * this

@JvmName("kilometerTimesSquareKilometer")
infix operator fun ScientificValue<PhysicalQuantity.Length, Kilometer>.times(area: ScientificValue<PhysicalQuantity.Area, SquareKilometer>) =
    area * this

@JvmName("kilometerTimesSquareMegameter")
infix operator fun ScientificValue<PhysicalQuantity.Length, Megameter>.times(area: ScientificValue<PhysicalQuantity.Area, SquareMegameter>) =
    area * this

@JvmName("gigameterTimesSquareGigameter")
infix operator fun ScientificValue<PhysicalQuantity.Length, Gigameter>.times(area: ScientificValue<PhysicalQuantity.Area, SquareGigameter>) =
    area * this

@JvmName("metricLengthTimesMetricArea")
infix operator fun <AreaUnit : MetricArea, HeightUnit : MetricLength> ScientificValue<PhysicalQuantity.Length, HeightUnit>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = area * this

@JvmName("inchTimesSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Length, Inch>.times(area: ScientificValue<PhysicalQuantity.Area, SquareInch>) =
    area * this

@JvmName("footTimesSquareFoot")
infix operator fun ScientificValue<PhysicalQuantity.Length, Foot>.times(area: ScientificValue<PhysicalQuantity.Area, SquareFoot>) =
    area * this

@JvmName("yardTimesSquareYard")
infix operator fun ScientificValue<PhysicalQuantity.Length, Yard>.times(area: ScientificValue<PhysicalQuantity.Area, SquareYard>) =
    area * this

@JvmName("mileTimesSquareMile")
infix operator fun ScientificValue<PhysicalQuantity.Length, Mile>.times(area: ScientificValue<PhysicalQuantity.Area, SquareMile>) =
    area * this

@JvmName("inchTimesAcre")
infix operator fun ScientificValue<PhysicalQuantity.Length, Inch>.times(area: ScientificValue<PhysicalQuantity.Area, Acre>) =
    area * this

@JvmName("footTimesAcre")
infix operator fun ScientificValue<PhysicalQuantity.Length, Foot>.times(area: ScientificValue<PhysicalQuantity.Area, Acre>) =
    area * this

@JvmName("imperialLengthTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea, HeightUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, HeightUnit>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = area * this

@JvmName("lengthTimesArea")
infix operator fun <AreaUnit : Area, HeightUnit : Length> ScientificValue<PhysicalQuantity.Length, HeightUnit>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = area * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.length

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.linearMassDensity.times
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.ImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.MetricLinearMassDensity
import com.splendo.kaluga.scientific.unit.UKImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.USCustomaryLinearMassDensity
import kotlin.jvm.JvmName

@JvmName("metricLengthTimesMetricLinearMassDensity")
infix operator fun <LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, MetricLinearMassDensity>
) = linearMassDensity * this

@JvmName("imperialLengthTimesImperialLinearMassDensity")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>
) = linearMassDensity * this

@JvmName("imperialLengthUKImperialLinearMassDensity")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, UKImperialLinearMassDensity>
) = linearMassDensity * this

@JvmName("imperialLengthTimesUSCustomaryLinearMassDensity")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, USCustomaryLinearMassDensity>
) = linearMassDensity * this

@JvmName("lengthTimesLinearMassDensity")
infix operator fun <LinearMassDensityUnit : LinearMassDensity, LengthUnit : Length> ScientificValue<PhysicalQuantity.Length, LengthUnit>.times(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>
) = linearMassDensity * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.linearMassDensity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("linearMassDensityFromAreaAndSpecificVolumeDefault")
fun <
    SpecificVolumeUnit : SpecificVolume,
    AreaUnit : Area,
    LinearMassDensityUnit : LinearMassDensity
    > LinearMassDensityUnit.linearMassDensity(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = linearMassDensity(area, specificVolume, ::DefaultScientificValue)

@JvmName("linearMassDensityFromAreaAndSpecificVolume")
fun <
    SpecificVolumeUnit : SpecificVolume,
    AreaUnit : Area,
    LinearMassDensityUnit : LinearMassDensity,
    Value : ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>
    > LinearMassDensityUnit.linearMassDensity(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    factory: (Decimal, LinearMassDensityUnit) -> Value
) = byDividing(area, specificVolume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.linearMassDensity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import kotlin.jvm.JvmName

@JvmName("linearMassDensityFromAreaDensityAndLengthDefault")
fun <
    AreaDensityUnit : AreaDensity,
    LengthUnit : Length,
    LinearMassDensityUnit : LinearMassDensity
    > LinearMassDensityUnit.linearMassDensity(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = linearMassDensity(areaDensity, length, ::DefaultScientificValue)

@JvmName("linearMassDensityFromAreaDensityAndLength")
fun <
    AreaDensityUnit : AreaDensity,
    LengthUnit : Length,
    LinearMassDensityUnit : LinearMassDensity,
    Value : ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>
    > LinearMassDensityUnit.linearMassDensity(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    factory: (Decimal, LinearMassDensityUnit) -> Value
) = byMultiplying(areaDensity, length, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.linearMassDensity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import kotlin.jvm.JvmName

@JvmName("linearMassDensityFromDensityAndLengthDefault")
fun <
    DensityUnit : Density,
    AreaUnit : Area,
    LinearMassDensityUnit : LinearMassDensity
    > LinearMassDensityUnit.linearMassDensity(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = linearMassDensity(density, area, ::DefaultScientificValue)

@JvmName("linearMassDensityFromDensityAndLength")
fun <
    DensityUnit : Density,
    AreaUnit : Area,
    LinearMassDensityUnit : LinearMassDensity,
    Value : ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>
    > LinearMassDensityUnit.linearMassDensity(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, LinearMassDensityUnit) -> Value
) = byMultiplying(density, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.linearMassDensity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("linearMassDensityFromWeightAndAreaDefault")
fun <
    WeightUnit : Weight,
    LengthUnit : Length,
    LinearMassDensityUnit : LinearMassDensity
    > LinearMassDensityUnit.linearMassDensity(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = linearMassDensity(weight, length, ::DefaultScientificValue)

@JvmName("linearMassDensityFromWeightAndArea")
fun <
    WeightUnit : Weight,
    LengthUnit : Length,
    LinearMassDensityUnit : LinearMassDensity,
    Value : ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>
    > LinearMassDensityUnit.linearMassDensity(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    factory: (Decimal, LinearMassDensityUnit) -> Value
) = byDividing(weight, length, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.linearMassDensity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.area
import com.splendo.kaluga.scientific.converter.specificVolume.times
import com.splendo.kaluga.scientific.converter.volume.div
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.ImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.MetricLinearMassDensity
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.UKImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.USCustomaryLinearMassDensity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import kotlin.jvm.JvmName

@JvmName("metricLinearMassDensityDivMetricDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, MetricLinearMassDensity>.div(
    density: ScientificValue<PhysicalQuantity.Density, MetricDensity>
) = (1(density.unit.per) / 1(unit.per)).unit.area(this, density)

@JvmName("imperialLinearMassDensityDivImperialDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>.div(
    density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>
) = (1(density.unit.per) / 1(unit.per)).unit.area(this, density)

@JvmName("imperialLinearMassDensityDivUKImperialDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>.div(
    density: ScientificValue<PhysicalQuantity.Density, UKImperialDensity>
) = (1(density.unit.per) / 1(unit.per)).unit.area(this, density)

@JvmName("imperialLinearMassDensityDivUSCustomaryDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>.div(
    density: ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>
) = (1(density.unit.per) / 1(unit.per)).unit.area(this, density)

@JvmName("ukImperialLinearMassDensityDivImperialDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, UKImperialLinearMassDensity>.div(
    density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>
) = (1(density.unit.per) / 1(unit.per)).unit.area(this, density)

@JvmName("ukImperialLinearMassDensityDivUKImperialDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, UKImperialLinearMassDensity>.div(
    density: ScientificValue<PhysicalQuantity.Density, UKImperialDensity>
) = (1(density.unit.per) / 1(unit.per)).unit.area(this, density)

@JvmName("usCustomaryLinearMassDensityDivImperialDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, USCustomaryLinearMassDensity>.div(
    density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>
) = (1(density.unit.per) / 1(unit.per)).unit.area(this, density)

@JvmName("usCustomaryLinearMassDensityDivUSCustomaryDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, USCustomaryLinearMassDensity>.div(
    density: ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>
) = (1(density.unit.per) / 1(unit.per)).unit.area(this, density)

@JvmName("linearMassDensityDivDensity")
infix operator fun <LinearMassDensityUnit : LinearMassDensity, DensityUnit : Density> ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = (1(density.unit.per) / 1(unit.per)).unit.area(this, density)

@JvmName("metricLinearMassDensityTimesMetricSpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, MetricLinearMassDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>
) = specificVolume * this

@JvmName("imperialLinearMassDensityTimesImperialSpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = specificVolume * this

@JvmName("imperialLinearMassDensityTimesUKImperialSpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>
) = specificVolume * this

@JvmName("imperialLinearMassDensityTimesUSCustomarySpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>
) = specificVolume * this

@JvmName("ukImperialLinearMassDensityTimesImperialSpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, UKImperialLinearMassDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = specificVolume * this

@JvmName("ukImperialLinearMassDensityTimesUKImperialSpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, UKImperialLinearMassDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>
) = specificVolume * this

@JvmName("usCustomaryLinearMassDensityTimesImperialSpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, USCustomaryLinearMassDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = specificVolume * this

@JvmName("usCustomaryLinearMassDensityTimesUSCustomarySpecificVolume")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, USCustomaryLinearMassDensity>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>
) = specificVolume * this

@JvmName("linearMassDensityTimesSpecificVolume")
infix operator fun <LinearMassDensityUnit : LinearMassDensity, SpecificVolumeUnit : SpecificVolume> ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = specificVolume * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.linearMassDensity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.areaDensity.areaDensity
import com.splendo.kaluga.scientific.converter.length.times
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.ImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.MetricLinearMassDensity
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.UKImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.USCustomaryLinearMassDensity
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricLinearMassDensityDivMetricLength")
infix operator fun <LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.LinearMassDensity, MetricLinearMassDensity>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) * 1(length.unit)).unit).areaDensity(this, length)

@JvmName("imperialLinearMassDensityDivImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) * 1(length.unit)).unit).areaDensity(this, length)

@JvmName("ukImperialLinearMassDensityDivImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.LinearMassDensity, UKImperialLinearMassDensity>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) * 1(length.unit)).unit).areaDensity(this, length)

@JvmName("usCustomaryLinearMassDensityDivImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.LinearMassDensity, USCustomaryLinearMassDensity>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit.weight per (1(unit.per) * 1(length.unit)).unit).areaDensity(this, length)

@JvmName("linearMassDensityDivLength")
infix operator fun <LinearMassDensityUnit : LinearMassDensity, LengthUnit : Length> ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (Kilogram per SquareMeter).areaDensity(this, length)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.linearMassDensity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.density.density
import com.splendo.kaluga.scientific.converter.length.times
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricLinearMassDensity
import com.splendo.kaluga.scientific.unit.UKImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.USCustomaryLinearMassDensity
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricLinearMassDensityDivMetricArea")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.LinearMassDensity, MetricLinearMassDensity>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit.weight per (1(unit.per) * 1(area.unit)).unit).density(this, area)

@JvmName("imperialLinearMassDensityDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit.weight per (1(unit.per) * 1(area.unit)).unit).density(this, area)

@JvmName("ukImperialLinearMassDensityDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.LinearMassDensity, UKImperialLinearMassDensity>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit.weight per (1(unit.per) * 1(area.unit)).unit).density(this, area)

@JvmName("usCustomaryLinearMassDensityDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.LinearMassDensity, USCustomaryLinearMassDensity>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit.weight per (1(unit.per) * 1(area.unit)).unit).density(this, area)

@JvmName("linearMassDensityDivArea")
infix operator fun <LinearMassDensityUnit : LinearMassDensity, AreaUnit : Area> ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (Kilogram per CubicMeter).density(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.linearMassDensity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.div
import com.splendo.kaluga.scientific.converter.length.length
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.ImperialAreaDensity
import com.splendo.kaluga.scientific.unit.ImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import com.splendo.kaluga.scientific.unit.MetricAreaDensity
import com.splendo.kaluga.scientific.unit.MetricLinearMassDensity
import com.splendo.kaluga.scientific.unit.UKImperialAreaDensity
import com.splendo.kaluga.scientific.unit.UKImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.USCustomaryAreaDensity
import com.splendo.kaluga.scientific.unit.USCustomaryLinearMassDensity
import kotlin.jvm.JvmName

@JvmName("metricLinearMassDensityDivMetricAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, MetricLinearMassDensity>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, MetricAreaDensity>
) = (1(areaDensity.unit.per) / 1(unit.per)).unit.length(this, areaDensity)

@JvmName("imperialLinearMassDensityDivImperialAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>
) = (1(areaDensity.unit.per) / 1(unit.per)).unit.length(this, areaDensity)

@JvmName("imperialLinearMassDensityDivUKImperialAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>
) = (1(areaDensity.unit.per) / 1(unit.per)).unit.length(this, areaDensity)

@JvmName("imperialLinearMassDensityDivUSCustomaryAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>
) = (1(areaDensity.unit.per) / 1(unit.per)).unit.length(this, areaDensity)

@JvmName("ukImperialLinearMassDensityDivImperialAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, UKImperialLinearMassDensity>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>
) = (1(areaDensity.unit.per) / 1(unit.per)).unit.length(this, areaDensity)

@JvmName("ukImperialLinearMassDensityDivUKImperialAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, UKImperialLinearMassDensity>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>
) = (1(areaDensity.unit.per) / 1(unit.per)).unit.length(this, areaDensity)

@JvmName("usCustomaryLinearMassDensityDivImperialAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, USCustomaryLinearMassDensity>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>
) = (1(areaDensity.unit.per) / 1(unit.per)).unit.length(this, areaDensity)

@JvmName("usCustomaryLinearMassDensityDivUSCustomaryAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.LinearMassDensity, USCustomaryLinearMassDensity>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>
) = (1(areaDensity.unit.per) / 1(unit.per)).unit.length(this, areaDensity)

@JvmName("linearMassDensityDivAreaDensity")
infix operator fun <LinearMassDensityUnit : LinearMassDensity, AreaDensityUnit : AreaDensity> ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>
) = (1(areaDensity.unit.per) / 1(unit.per)).unit.length(this, areaDensity)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.linearMassDensity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.weight.mass
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.ImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.MetricLinearMassDensity
import com.splendo.kaluga.scientific.unit.UKImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.USCustomaryLinearMassDensity
import kotlin.jvm.JvmName

@JvmName("metricLinearMassDensityTimesMetricLength")
infix operator fun <LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.LinearMassDensity, MetricLinearMassDensity>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = unit.weight.mass(this, length)

@JvmName("imperialLinearMassDensityTimesImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = unit.weight.mass(this, length)

@JvmName("ukImperialLinearMassDensityTimesImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.LinearMassDensity, UKImperialLinearMassDensity>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = unit.weight.mass(this, length)

@JvmName("usCustomaryLinearMassDensityTimesImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.LinearMassDensity, USCustomaryLinearMassDensity>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = unit.weight.mass(this, length)

@JvmName("linearMassDensityTimesLength")
infix operator fun <LinearMassDensityUnit : LinearMassDensity, LengthUnit : Length> ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = Kilogram.mass(this, length)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.Luminance
import com.splendo.kaluga.scientific.unit.SolidAngle
import kotlin.jvm.JvmName

@JvmName("luminanceFromIlluminanceAndSolidAngleDefault")
fun <
    LuminanceUnit : Luminance,
    SolidAngleUnit : SolidAngle,
    IlluminanceUnit : Illuminance
    > LuminanceUnit.luminance(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>,
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
) = luminance(illuminance, solidAngle, ::DefaultScientificValue)

@JvmName("luminanceFromIlluminanceAndSolidAngle")
fun <
    LuminanceUnit : Luminance,
    SolidAngleUnit : SolidAngle,
    IlluminanceUnit : Illuminance,
    Value : ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>
    > LuminanceUnit.luminance(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>,
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>,
    factory: (Decimal, LuminanceUnit) -> Value
) = byDividing(illuminance, solidAngle, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Luminance
import com.splendo.kaluga.scientific.unit.LuminousIntensity
import kotlin.jvm.JvmName

@JvmName("luminanceFromLuminousIntensityAndAreaDefault")
fun <
    LuminousIntensityUnit : LuminousIntensity,
    AreaUnit : Area,
    LuminanceUnit : Luminance
    > LuminanceUnit.luminance(
    luminousIntensity: ScientificValue<PhysicalQuantity.LuminousIntensity, LuminousIntensityUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = luminance(luminousIntensity, area, ::DefaultScientificValue)

@JvmName("luminanceFromLuminousIntensityAndArea")
fun <
    LuminousIntensityUnit : LuminousIntensity,
    AreaUnit : Area,
    LuminanceUnit : Luminance,
    Value : ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>
    > LuminanceUnit.luminance(
    luminousIntensity: ScientificValue<PhysicalQuantity.LuminousIntensity, LuminousIntensityUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, LuminanceUnit) -> Value
) = byDividing(luminousIntensity, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.illuminance.illuminance
import com.splendo.kaluga.scientific.unit.FootCandle
import com.splendo.kaluga.scientific.unit.ImperialLuminance
import com.splendo.kaluga.scientific.unit.Lambert
import com.splendo.kaluga.scientific.unit.Luminance
import com.splendo.kaluga.scientific.unit.Lux
import com.splendo.kaluga.scientific.unit.MetricLuminance
import com.splendo.kaluga.scientific.unit.Phot
import com.splendo.kaluga.scientific.unit.SolidAngle
import com.splendo.kaluga.scientific.unit.Stilb
import kotlin.jvm.JvmName

@JvmName("stilbTimesSolidAngle")
infix operator fun <SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.Luminance, Stilb>.times(
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
) = Phot.illuminance(this, solidAngle)

@JvmName("lambertTimesSolidAngle")
infix operator fun <SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.Luminance, Lambert>.times(
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
) = Phot.illuminance(this, solidAngle)

@JvmName("metricLuminanceTimesSolidAngle")
infix operator fun <LuminanceUnit : MetricLuminance, SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>.times(
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
) = Lux.illuminance(this, solidAngle)

@JvmName("imperialLuminanceTimesSolidAngle")
infix operator fun <LuminanceUnit : ImperialLuminance, SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>.times(
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
) = FootCandle.illuminance(this, solidAngle)

@JvmName("luminanceTimesSolidAngle")
infix operator fun <LuminanceUnit : Luminance, SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>.times(
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
) = Lux.illuminance(this, solidAngle)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.luminousIntensity.luminousIntensity
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Candela
import com.splendo.kaluga.scientific.unit.Luminance
import kotlin.jvm.JvmName

@JvmName("luminanceTimesArea")
infix operator fun <LuminanceUnit : Luminance, AreaUnit : Area> ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Candela.luminousIntensity(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousEnergy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.LuminousEnergy
import com.splendo.kaluga.scientific.unit.LuminousExposure
import kotlin.jvm.JvmName

@JvmName("luminousEnergyFromLuminousExposureAndAreaDefault")
fun <
    LuminousExposureUnit : LuminousExposure,
    AreaUnit : Area
    > LuminousEnergy.luminousEnergy(
    luminousExposure: ScientificValue<PhysicalQuantity.LuminousExposure, LuminousExposureUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = luminousEnergy(luminousExposure, area, ::DefaultScientificValue)

@JvmName("luminousEnergyFromLuminousExposureAndArea")
fun <
    LuminousExposureUnit : LuminousExposure,
    AreaUnit : Area,
    Value : ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>
    > LuminousEnergy.luminousEnergy(
    luminousExposure: ScientificValue<PhysicalQuantity.LuminousExposure, LuminousExposureUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, LuminousEnergy) -> Value
) = byMultiplying(luminousExposure, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousEnergy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.LuminousEnergy
import com.splendo.kaluga.scientific.unit.LuminousFlux
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("luminousEnergyFromLuminousFluxAndTimeDefault")
fun <
    TimeUnit : Time,
    LuminousFluxUnit : LuminousFlux
    > LuminousEnergy.luminousEnergy(
    luminousFlux: ScientificValue<PhysicalQuantity.LuminousFlux, LuminousFluxUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = luminousEnergy(luminousFlux, time, ::DefaultScientificValue)

@JvmName("luminousEnergyFromLuminousFluxAndTime")
fun <
    TimeUnit : Time,
    LuminousFluxUnit : LuminousFlux,
    Value : ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>
    > LuminousEnergy.luminousEnergy(
    luminousFlux: ScientificValue<PhysicalQuantity.LuminousFlux, LuminousFluxUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, LuminousEnergy) -> Value
) = byMultiplying(luminousFlux, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousEnergy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.area
import com.splendo.kaluga.scientific.converter.luminousFlux.div
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.ImperialLuminousExposure
import com.splendo.kaluga.scientific.unit.LuminousEnergy
import com.splendo.kaluga.scientific.unit.LuminousExposure
import com.splendo.kaluga.scientific.unit.MetricLuminousExposure
import kotlin.jvm.JvmName

@JvmName("luminousEnergyDivMetricExposure")
infix operator fun ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>.div(
    luminousExposure: ScientificValue<PhysicalQuantity.LuminousExposure, MetricLuminousExposure>
) = (1(unit.luminousFlux) / 1(luminousExposure.unit.illuminance)).unit.area(this, luminousExposure)

@JvmName("luminousEnergyDivImperialExposure")
infix operator fun ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>.div(
    luminousExposure: ScientificValue<PhysicalQuantity.LuminousExposure, ImperialLuminousExposure>
) = (1(unit.luminousFlux) / 1(luminousExposure.unit.illuminance)).unit.area(this, luminousExposure)

@JvmName("luminousEnergyDivExposure")
infix operator fun <ExposureUnit : LuminousExposure> ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>.div(
    luminousExposure: ScientificValue<PhysicalQuantity.LuminousExposure, ExposureUnit>
) = (1(unit.luminousFlux) / 1(luminousExposure.unit.illuminance)).unit.area(this, luminousExposure)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousEnergy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.luminousExposure.luminousExposure
import com.splendo.kaluga.scientific.converter.luminousFlux.div
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.LuminousEnergy
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.x
import kotlin.jvm.JvmName

@JvmName("luminousEnergyDivMetricArea")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = ((1(unit.luminousFlux) / 1(area.unit)).unit x unit.time).luminousExposure(this, area)

@JvmName("luminousEnergyDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = ((1(unit.luminousFlux) / 1(area.unit)).unit x unit.time).luminousExposure(this, area)

@JvmName("luminousEnergyDivArea")
infix operator fun <AreaUnit : Area> ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = ((1(unit.luminousFlux) / 1(area.unit)).unit x unit.time).luminousExposure(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousEnergy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.luminousFlux.luminousFlux
import com.splendo.kaluga.scientific.unit.LuminousEnergy
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("luminousEnergyDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.luminousFlux.luminousFlux(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousEnergy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.LuminousEnergy
import com.splendo.kaluga.scientific.unit.LuminousFlux
import kotlin.jvm.JvmName

@JvmName("luminousEnergyDivLuminousFlux")
infix operator fun <LuminousFluxUnit : LuminousFlux> ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>.div(
    luminousFlux: ScientificValue<PhysicalQuantity.LuminousFlux, LuminousFluxUnit>
) = unit.time.time(this, luminousFlux)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousExposure

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.LuminousExposure
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("luminousExposureFromIlluminanceAndTimeDefault")
fun <
    LuminousExposureUnit : LuminousExposure,
    TimeUnit : Time,
    IlluminanceUnit : Illuminance
    > LuminousExposureUnit.luminousExposure(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = luminousExposure(illuminance, time, ::DefaultScientificValue)

@JvmName("luminousExposureFromIlluminanceAndTime")
fun <
    LuminousExposureUnit : LuminousExposure,
    TimeUnit : Time,
    IlluminanceUnit : Illuminance,
    Value : ScientificValue<PhysicalQuantity.LuminousExposure, LuminousExposureUnit>
    > LuminousExposureUnit.luminousExposure(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, LuminousExposureUnit) -> Value
) = byMultiplying(illuminance, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousExposure

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.LuminousEnergy
import com.splendo.kaluga.scientific.unit.LuminousExposure
import kotlin.jvm.JvmName

@JvmName("luminousExposureFromLuminousEnergyAndAreaDefault")
fun <
    LuminousExposureUnit : LuminousExposure,
    AreaUnit : Area
    > LuminousExposureUnit.luminousExposure(
    luminousEnergy: ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = luminousExposure(luminousEnergy, area, ::DefaultScientificValue)

@JvmName("luminousExposureFromLuminousEnergyAndArea")
fun <
    LuminousExposureUnit : LuminousExposure,
    AreaUnit : Area,
    Value : ScientificValue<PhysicalQuantity.LuminousExposure, LuminousExposureUnit>
    > LuminousExposureUnit.luminousExposure(
    luminousEnergy: ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, LuminousExposureUnit) -> Value
) = byDividing(luminousEnergy, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousExposure

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.illuminance.illuminance
import com.splendo.kaluga.scientific.unit.ImperialLuminousExposure
import com.splendo.kaluga.scientific.unit.LuminousExposure
import com.splendo.kaluga.scientific.unit.MetricLuminousExposure
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("metricLuminousExposureDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.LuminousExposure, MetricLuminousExposure>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.illuminance.illuminance(this, time)

@JvmName("imperialLuminousExposureDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.LuminousExposure, ImperialLuminousExposure>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.illuminance.illuminance(this, time)

@JvmName("luminousExposureDivTime")
infix operator fun <LuminousExposureUnit : LuminousExposure, TimeUnit : Time> ScientificValue<PhysicalQuantity.LuminousExposure, LuminousExposureUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.illuminance.illuminance(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousExposure

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.illuminance.times
import com.splendo.kaluga.scientific.converter.luminousEnergy.luminousEnergy
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialLuminousExposure
import com.splendo.kaluga.scientific.unit.LuminousExposure
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricLuminousExposure
import com.splendo.kaluga.scientific.unit.x
import kotlin.jvm.JvmName

@JvmName("metricExposureTimesMetricArea")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.LuminousExposure, MetricLuminousExposure>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = ((1(unit.illuminance) * 1(area.unit)).unit x unit.time).luminousEnergy(this, area)

@JvmName("imperialExposureTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.LuminousExposure, ImperialLuminousExposure>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = ((1(unit.illuminance) * 1(area.unit)).unit x unit.time).luminousEnergy(this, area)

@JvmName("exposureTimesArea")
infix operator fun <ExposureUnit : LuminousExposure, AreaUnit : Area> ScientificValue<PhysicalQuantity.LuminousExposure, ExposureUnit>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = ((1(unit.illuminance) * 1(area.unit)).unit x unit.time).luminousEnergy(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousExposure

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.ImperialIlluminance
import com.splendo.kaluga.scientific.unit.ImperialLuminousExposure
import com.splendo.kaluga.scientific.unit.LuminousExposure
import com.splendo.kaluga.scientific.unit.MetricIlluminance
import com.splendo.kaluga.scientific.unit.MetricLuminousExposure
import kotlin.jvm.JvmName

@JvmName("metricLuminousExposureDivMetricIlluminance")
infix operator fun <IlluminanceUnit : MetricIlluminance> ScientificValue<PhysicalQuantity.LuminousExposure, MetricLuminousExposure>.div(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>
) = unit.time.time(this, illuminance)

@JvmName("imperialLuminousExposureDivImperialIlluminance")
infix operator fun <IlluminanceUnit : ImperialIlluminance> ScientificValue<PhysicalQuantity.LuminousExposure, ImperialLuminousExposure>.div(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>
) = unit.time.time(this, illuminance)

@JvmName("luminousExposureDivIlluminance")
infix operator fun <IlluminanceUnit : Illuminance> ScientificValue<PhysicalQuantity.LuminousExposure, LuminousExposure>.div(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>
) = unit.time.time(this, illuminance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousFlux

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.LuminousFlux
import kotlin.jvm.JvmName

@JvmName("luminousFluxFromIlluminanceAndAreaDefault")
fun <
    FluxUnit : LuminousFlux,
    AreaUnit : Area,
    IlluminanceUnit : Illuminance
    > FluxUnit.flux(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = flux(illuminance, area, ::DefaultScientificValue)

@JvmName("luminousFluxFromIlluminanceAndArea")
fun <
    FluxUnit : LuminousFlux,
    AreaUnit : Area,
    IlluminanceUnit : Illuminance,
    Value : ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>
    > FluxUnit.flux(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, FluxUnit) -> Value
) = byMultiplying(illuminance, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousFlux

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.LuminousEnergy
import com.splendo.kaluga.scientific.unit.LuminousFlux
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("luminousFluxFromLuminousEnergyAndTimeDefault")
fun <
    TimeUnit : Time,
    LuminousFluxUnit : LuminousFlux
    > LuminousFluxUnit.luminousFlux(
    luminousEnergy: ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = luminousFlux(luminousEnergy, time, ::DefaultScientificValue)

@JvmName("luminousFluxFromLuminousEnergyAndTime")
fun <
    TimeUnit : Time,
    LuminousFluxUnit : LuminousFlux,
    Value : ScientificValue<PhysicalQuantity.LuminousFlux, LuminousFluxUnit>
    > LuminousFluxUnit.luminousFlux(
    luminousEnergy: ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, LuminousFluxUnit) -> Value
) = byDividing(luminousEnergy, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousFlux

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.LuminousFlux
import com.splendo.kaluga.scientific.unit.LuminousIntensity
import com.splendo.kaluga.scientific.unit.SolidAngle
import kotlin.jvm.JvmName

@JvmName("luminousFluxFromIntensityAndSolidAngleDefault")
fun <
    IntensityUnit : LuminousIntensity,
    SolidAngleUnit : SolidAngle,
    FluxUnit : LuminousFlux
    > FluxUnit.flux(
    intensity: ScientificValue<PhysicalQuantity.LuminousIntensity, IntensityUnit>,
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
) = flux(intensity, solidAngle, ::DefaultScientificValue)

@JvmName("luminousFluxFromIntensityAndSolidAngle")
fun <
    IntensityUnit : LuminousIntensity,
    SolidAngleUnit : SolidAngle,
    FluxUnit : LuminousFlux,
    Value : ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>
    > FluxUnit.flux(
    intensity: ScientificValue<PhysicalQuantity.LuminousIntensity, IntensityUnit>,
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>,
    factory: (Decimal, FluxUnit) -> Value
) = byMultiplying(intensity, solidAngle, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousFlux

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.area
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.ImperialIlluminance
import com.splendo.kaluga.scientific.unit.LuminousFlux
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Phot
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.SquareFoot
import com.splendo.kaluga.scientific.unit.SquareMeter
import kotlin.jvm.JvmName

@JvmName("fluxDivPhot")
infix operator fun <FluxUnit : LuminousFlux> ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>.div(
    phot: ScientificValue<PhysicalQuantity.Illuminance, Phot>
) = SquareCentimeter.area(this, phot)

@JvmName("fluxDivPhotMultiple")
infix operator fun <FluxUnit : LuminousFlux, PhotUnit> ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>.div(
    phot: ScientificValue<PhysicalQuantity.Illuminance, PhotUnit>
) where PhotUnit : Illuminance, PhotUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Illuminance, Phot> =
    SquareCentimeter.area(this, phot)

@JvmName("fluxDivImperialIlluminance")
infix operator fun <FluxUnit : LuminousFlux, IlluminanceUnit : ImperialIlluminance> ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>.div(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>
) = SquareFoot.area(this, illuminance)

@JvmName("fluxDivIlluminance")
infix operator fun <FluxUnit : LuminousFlux, IlluminanceUnit : Illuminance> ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>.div(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>
) = SquareMeter.area(this, illuminance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousFlux

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.illuminance.illuminance
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.FootCandle
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.LuminousFlux
import com.splendo.kaluga.scientific.unit.Lux
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.Phot
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import kotlin.jvm.JvmName

@JvmName("lumenDivSquareCentimeter")
infix operator fun <FluxUnit : LuminousFlux> ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>.div(
    squareCentimeter: ScientificValue<PhysicalQuantity.Area, SquareCentimeter>
) = Phot.illuminance(this, squareCentimeter)

@JvmName("lumenDivMetricArea")
infix operator fun <FluxUnit : LuminousFlux, AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Lux.illuminance(this, area)

@JvmName("lumenDivImperialArea")
infix operator fun <FluxUnit : LuminousFlux, AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = FootCandle.illuminance(this, area)

@JvmName("lumenDivArea")
infix operator fun <FluxUnit : LuminousFlux, AreaUnit : Area> ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Lux.illuminance(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousFlux

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.luminousEnergy.luminousEnergy
import com.splendo.kaluga.scientific.unit.LuminousFlux
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.x
import kotlin.jvm.JvmName

@JvmName("LuminousFluxTimesTime")
infix operator fun <LuminousFluxUnit : LuminousFlux, TimeUnit : Time> ScientificValue<PhysicalQuantity.LuminousFlux, LuminousFluxUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit x time.unit).luminousEnergy(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousFlux

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.luminousIntensity.luminousIntensity
import com.splendo.kaluga.scientific.unit.Candela
import com.splendo.kaluga.scientific.unit.LuminousFlux
import com.splendo.kaluga.scientific.unit.SolidAngle
import kotlin.jvm.JvmName

@JvmName("luminousFluxDivSolidAngle")
infix operator fun <FluxUnit : LuminousFlux, SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>.div(
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
) = Candela.luminousIntensity(this, solidAngle)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousFlux

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.solidAngle.solidAngle
import com.splendo.kaluga.scientific.unit.LuminousFlux
import com.splendo.kaluga.scientific.unit.LuminousIntensity
import com.splendo.kaluga.scientific.unit.Steradian
import kotlin.jvm.JvmName

@JvmName("luminousFluxDivIntensity")
infix operator fun <FluxUnit : LuminousFlux, IntensityUnit : LuminousIntensity> ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>.div(
    intensity: ScientificValue<PhysicalQuantity.LuminousIntensity, IntensityUnit>
) = Steradian.solidAngle(this, intensity)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousIntensity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Luminance
import com.splendo.kaluga.scientific.unit.LuminousIntensity
import kotlin.jvm.JvmName

@JvmName("luminousIntensityFromLuminanceAndAreaDefault")
fun <
    LuminousIntensityUnit : LuminousIntensity,
    AreaUnit : Area,
    LuminanceUnit : Luminance
    > LuminousIntensityUnit.luminousIntensity(
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = luminousIntensity(luminance, area, ::DefaultScientificValue)

@JvmName("luminousIntensityFromLuminanceAndArea")
fun <
    LuminousIntensityUnit : LuminousIntensity,
    AreaUnit : Area,
    LuminanceUnit : Luminance,
    Value : ScientificValue<PhysicalQuantity.LuminousIntensity, LuminousIntensityUnit>
    > LuminousIntensityUnit.luminousIntensity(
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, LuminousIntensityUnit) -> Value
) = byMultiplying(luminance, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousIntensity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.LuminousFlux
import com.splendo.kaluga.scientific.unit.LuminousIntensity
import com.splendo.kaluga.scientific.unit.SolidAngle
import kotlin.jvm.JvmName

@JvmName("luminousIntensityFromLuminousFluxAndSolidAngleDefault")
fun <
    IntensityUnit : LuminousIntensity,
    SolidAngleUnit : SolidAngle,
    FluxUnit : LuminousFlux
    > IntensityUnit.luminousIntensity(
    flux: ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>,
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
) = luminousIntensity(flux, solidAngle, ::DefaultScientificValue)

@JvmName("luminousIntensityFromLuminousFluxAndSolidAngle")
fun <
    IntensityUnit : LuminousIntensity,
    SolidAngleUnit : SolidAngle,
    FluxUnit : LuminousFlux,
    Value : ScientificValue<PhysicalQuantity.LuminousIntensity, IntensityUnit>
    > IntensityUnit.luminousIntensity(
    flux: ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>,
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>,
    factory: (Decimal, IntensityUnit) -> Value
) = byDividing(flux, solidAngle, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousIntensity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.area
import com.splendo.kaluga.scientific.unit.ImperialLuminance
import com.splendo.kaluga.scientific.unit.Lambert
import com.splendo.kaluga.scientific.unit.Luminance
import com.splendo.kaluga.scientific.unit.LuminousIntensity
import com.splendo.kaluga.scientific.unit.MetricLuminance
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.SquareFoot
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.Stilb
import kotlin.jvm.JvmName

@JvmName("luminousIntensityDivStilb")
infix operator fun <LuminousIntensityUnit : LuminousIntensity> ScientificValue<PhysicalQuantity.LuminousIntensity, LuminousIntensityUnit>.div(
    luminance: ScientificValue<PhysicalQuantity.Luminance, Stilb>
) = SquareCentimeter.area(this, luminance)

@JvmName("luminousIntensityDivLambert")
infix operator fun <LuminousIntensityUnit : LuminousIntensity> ScientificValue<PhysicalQuantity.LuminousIntensity, LuminousIntensityUnit>.div(
    luminance: ScientificValue<PhysicalQuantity.Luminance, Lambert>
) = SquareCentimeter.area(this, luminance)

@JvmName("luminousIntensityDivMetricLuminance")
infix operator fun <LuminousIntensityUnit : LuminousIntensity, LuminanceUnit : MetricLuminance> ScientificValue<PhysicalQuantity.LuminousIntensity, LuminousIntensityUnit>.div(
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>
) = SquareMeter.area(this, luminance)

@JvmName("luminousIntensityDivImperialLuminance")
infix operator fun <LuminousIntensityUnit : LuminousIntensity, LuminanceUnit : ImperialLuminance> ScientificValue<PhysicalQuantity.LuminousIntensity, LuminousIntensityUnit>.div(
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>
) = SquareFoot.area(this, luminance)

@JvmName("luminousIntensityDivLuminance")
infix operator fun <LuminousIntensityUnit : LuminousIntensity, LuminanceUnit : Luminance> ScientificValue<PhysicalQuantity.LuminousIntensity, LuminousIntensityUnit>.div(
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>
) = SquareMeter.area(this, luminance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousIntensity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.luminance.luminance
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.FootLambert
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.LuminousIntensity
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.Nit
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.Stilb
import kotlin.jvm.JvmName

@JvmName("luminousIntensityDivSquareCentimeter")
infix operator fun <LuminousIntensityUnit : LuminousIntensity> ScientificValue<PhysicalQuantity.LuminousIntensity, LuminousIntensityUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, SquareCentimeter>
) = Stilb.luminance(this, area)

@JvmName("luminousIntensityDivMetricArea")
infix operator fun <LuminousIntensityUnit : LuminousIntensity, AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.LuminousIntensity, LuminousIntensityUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Nit.luminance(this, area)

@JvmName("luminousIntensityDivImperialArea")
infix operator fun <LuminousIntensityUnit : LuminousIntensity, AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.LuminousIntensity, LuminousIntensityUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = FootLambert.luminance(this, area)

@JvmName("luminousIntensityDivArea")
infix operator fun <LuminousIntensityUnit : LuminousIntensity, AreaUnit : Area> ScientificValue<PhysicalQuantity.LuminousIntensity, LuminousIntensityUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Nit.luminance(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.luminousIntensity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.luminousFlux.flux
import com.splendo.kaluga.scientific.unit.Lumen
import com.splendo.kaluga.scientific.unit.LuminousIntensity
import com.splendo.kaluga.scientific.unit.SolidAngle
import kotlin.jvm.JvmName

@JvmName("luminousIntensityTimesSolidAngle")
infix operator fun <IntensityUnit : LuminousIntensity, SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.LuminousIntensity, IntensityUnit>.times(
    solidAngle: ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
) = Lumen.flux(this, solidAngle)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticFlux

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.ElectricInductance
import com.splendo.kaluga.scientific.unit.MagneticFlux
import kotlin.jvm.JvmName

@JvmName("fluxFromInductanceAndCurrentDefault")
fun <
    FluxUnit : MagneticFlux,
    CurrentUnit : ElectricCurrent,
    InductanceUnit : ElectricInductance
    > FluxUnit.flux(
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
) = flux(inductance, current, ::DefaultScientificValue)

@JvmName("fluxFromInductanceAndCurrent")
fun <
    FluxUnit : MagneticFlux,
    CurrentUnit : ElectricCurrent,
    InductanceUnit : ElectricInductance,
    Value : ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>
    > FluxUnit.flux(
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>,
    factory: (Decimal, FluxUnit) -> Value
) = byMultiplying(inductance, current, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticFlux

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.MagneticFlux
import kotlin.jvm.JvmName

@JvmName("fluxFromResistanceAndChargeDefault")
fun <
    ResistanceUnit : ElectricResistance,
    ChargeUnit : ElectricCharge,
    FluxUnit : MagneticFlux
    > FluxUnit.flux(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>,
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>
) = flux(resistance, charge, ::DefaultScientificValue)

@JvmName("fluxFromResistanceAndCharge")
fun <
    ResistanceUnit : ElectricResistance,
    ChargeUnit : ElectricCharge,
    FluxUnit : MagneticFlux,
    Value : ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>
    > FluxUnit.flux(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>,
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>,
    factory: (Decimal, FluxUnit) -> Value
) = byMultiplying(resistance, charge, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticFlux

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.MagneticFlux
import kotlin.jvm.JvmName

@JvmName("fluxFromEnergyAndCurrentDefault")
fun <
    EnergyUnit : Energy,
    CurrentUnit : ElectricCurrent,
    FluxUnit : MagneticFlux
    > FluxUnit.flux(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
) = flux(energy, current, ::DefaultScientificValue)

@JvmName("fluxFromEnergyAndCurrent")
fun <
    EnergyUnit : Energy,
    CurrentUnit : ElectricCurrent,
    FluxUnit : MagneticFlux,
    Value : ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>
    > FluxUnit.flux(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>,
    factory: (Decimal, FluxUnit) -> Value
) = byDividing(energy, current, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticFlux

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.MagneticInduction
import kotlin.jvm.JvmName

@JvmName("fluxFromInductionAndAreaDefault")
fun <
    FluxUnit : MagneticFlux,
    AreaUnit : Area,
    InductionUnit : MagneticInduction
    > FluxUnit.flux(
    induction: ScientificValue<PhysicalQuantity.MagneticInduction, InductionUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = flux(induction, area, ::DefaultScientificValue)

@JvmName("fluxFromInductionAndArea")
fun <
    FluxUnit : MagneticFlux,
    AreaUnit : Area,
    InductionUnit : MagneticInduction,
    Value : ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>
    > FluxUnit.flux(
    induction: ScientificValue<PhysicalQuantity.MagneticInduction, InductionUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, FluxUnit) -> Value
) = byMultiplying(induction, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticFlux

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("fluxFromVoltageAndTimeDefault")
fun <
    VoltageUnit : Voltage,
    TimeUnit : Time,
    FluxUnit : MagneticFlux
    > FluxUnit.flux(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = flux(voltage, time, ::DefaultScientificValue)

@JvmName("fluxFromVoltageAndTime")
fun <
    VoltageUnit : Voltage,
    TimeUnit : Time,
    FluxUnit : MagneticFlux,
    Value : ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>
    > FluxUnit.flux(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, FluxUnit) -> Value
) = byMultiplying(voltage, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticFlux

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.area
import com.splendo.kaluga.scientific.unit.Gauss
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.MagneticInduction
import com.splendo.kaluga.scientific.unit.Maxwell
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.SquareMeter
import kotlin.jvm.JvmName

@JvmName("maxwellDivGauss")
infix operator fun ScientificValue<PhysicalQuantity.MagneticFlux, Maxwell>.div(induction: ScientificValue<PhysicalQuantity.MagneticInduction, Gauss>) =
    SquareCentimeter.area(this, induction)

@JvmName("fluxDivInduction")
infix operator fun <FluxUnit : MagneticFlux, InductionUnit : MagneticInduction> ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>.div(
    induction: ScientificValue<PhysicalQuantity.MagneticInduction, InductionUnit>
) = SquareMeter.area(this, induction)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticFlux

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCharge.charge
import com.splendo.kaluga.scientific.unit.Abcoulomb
import com.splendo.kaluga.scientific.unit.Abohm
import com.splendo.kaluga.scientific.unit.Coulomb
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.Maxwell
import kotlin.jvm.JvmName

@JvmName("maxwellDivAbohm")
infix operator fun ScientificValue<PhysicalQuantity.MagneticFlux, Maxwell>.div(resistance: ScientificValue<PhysicalQuantity.ElectricResistance, Abohm>) =
    Abcoulomb.charge(this, resistance)

@JvmName("fluxDivResistance")
infix operator fun <FluxUnit : MagneticFlux, ResistanceUnit : ElectricResistance> ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>.div(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
) = Coulomb.charge(this, resistance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticFlux

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCurrent.current
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Abhenry
import com.splendo.kaluga.scientific.unit.Ampere
import com.splendo.kaluga.scientific.unit.ElectricInductance
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.Maxwell
import kotlin.jvm.JvmName

@JvmName("maxwellDivAbhenry")
infix operator fun ScientificValue<PhysicalQuantity.MagneticFlux, Maxwell>.div(inductance: ScientificValue<PhysicalQuantity.ElectricInductance, Abhenry>) =
    Abampere.current(this, inductance)

@JvmName("fluxDivInductance")
infix operator fun <FluxUnit : MagneticFlux, InductanceUnit : ElectricInductance> ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>.div(
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>
) = Ampere.current(this, inductance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticFlux

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricInductance.inductance
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Abhenry
import com.splendo.kaluga.scientific.unit.Biot
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Henry
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.Maxwell
import kotlin.jvm.JvmName

@JvmName("maxwellDivAbampere")
infix operator fun ScientificValue<PhysicalQuantity.MagneticFlux, Maxwell>.div(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>) =
    Abhenry.inductance(this, current)

@JvmName("maxwellDivBiot")
infix operator fun ScientificValue<PhysicalQuantity.MagneticFlux, Maxwell>.div(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>) =
    Abhenry.inductance(this, current)

@JvmName("fluxDivCurrent")
infix operator fun <FluxUnit : MagneticFlux, CurrentUnit : ElectricCurrent> ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>.div(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
) = Henry.inductance(this, current)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticFlux

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricResistance.resistance
import com.splendo.kaluga.scientific.unit.Abcoulomb
import com.splendo.kaluga.scientific.unit.Abohm
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.Maxwell
import com.splendo.kaluga.scientific.unit.Ohm
import kotlin.jvm.JvmName

@JvmName("maxwellDivAbcoulomb")
infix operator fun ScientificValue<PhysicalQuantity.MagneticFlux, Maxwell>.div(charge: ScientificValue<PhysicalQuantity.ElectricCharge, Abcoulomb>) =
    Abohm.resistance(this, charge)

@JvmName("fluxDivCharge")
infix operator fun <FluxUnit : MagneticFlux, ChargeUnit : ElectricCharge> ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>.div(
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>
) = Ohm.resistance(this, charge)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticFlux

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.energy.energy
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Biot
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.Maxwell
import kotlin.jvm.JvmName

@JvmName("maxwellTimesAbampere")
infix operator fun ScientificValue<PhysicalQuantity.MagneticFlux, Maxwell>.times(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>) =
    Erg.energy(this, current)

@JvmName("maxwellTimesBiot")
infix operator fun ScientificValue<PhysicalQuantity.MagneticFlux, Maxwell>.times(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>) =
    Erg.energy(this, current)

@JvmName("fluxTimesCurrent")
infix operator fun <FluxUnit : MagneticFlux, CurrentUnit : ElectricCurrent> ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>.times(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
) = Joule.energy(this, current)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticFlux

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.magneticInduction.induction
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Gauss
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.Maxwell
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.Tesla
import kotlin.jvm.JvmName

@JvmName("maxwellDivSquareCentimeter")
infix operator fun ScientificValue<PhysicalQuantity.MagneticFlux, Maxwell>.div(area: ScientificValue<PhysicalQuantity.Area, SquareCentimeter>) =
    Gauss.induction(this, area)

@JvmName("fluxDivArea")
infix operator fun <FluxUnit : MagneticFlux, AreaUnit : Area> ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Tesla.induction(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticFlux

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("fluxDivVoltage")
infix operator fun <FluxUnit : MagneticFlux, VoltageUnit : Voltage> ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>.div(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = Second.time(this, voltage)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticFlux

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.voltage.voltage
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.Maxwell
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Volt
import kotlin.jvm.JvmName

@JvmName("maxwellDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.MagneticFlux, Maxwell>.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    Abvolt.voltage(this, time)

@JvmName("fluxDivTime")
infix operator fun <FluxUnit : MagneticFlux, TimeUnit : Time> ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Volt.voltage(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticInduction

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.MagneticInduction
import kotlin.jvm.JvmName

@JvmName("inductionFromFluxAndAreaDefault")
fun <
    FluxUnit : MagneticFlux,
    AreaUnit : Area,
    InductionUnit : MagneticInduction
    > InductionUnit.induction(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = induction(flux, area, ::DefaultScientificValue)

@JvmName("inductionFromFluxAndArea")
fun <
    FluxUnit : MagneticFlux,
    AreaUnit : Area,
    InductionUnit : MagneticInduction,
    Value : ScientificValue<PhysicalQuantity.MagneticInduction, InductionUnit>
    > InductionUnit.induction(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, InductionUnit) -> Value
) = byDividing(flux, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.magneticInduction

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.magneticFlux.flux
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Gauss
import com.splendo.kaluga.scientific.unit.MagneticInduction
import com.splendo.kaluga.scientific.unit.Maxwell
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.Weber
import kotlin.jvm.JvmName

@JvmName("gaussTimesSquareCentimeter")
infix operator fun ScientificValue<PhysicalQuantity.MagneticInduction, Gauss>.times(area: ScientificValue<PhysicalQuantity.Area, SquareCentimeter>) =
    Maxwell.flux(this, area)

@JvmName("inductionTimesArea")
infix operator fun <InductionUnit : MagneticInduction, AreaUnit : Area> ScientificValue<PhysicalQuantity.MagneticInduction, InductionUnit>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Weber.flux(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.massFlowRate

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.MassFlowRate
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("massFlowRateFromWeightAndAreaDefault")
fun <
    WeightUnit : Weight,
    TimeUnit : Time,
    MassFlowRateUnit : MassFlowRate
    > MassFlowRateUnit.massFlowRate(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = massFlowRate(weight, time, ::DefaultScientificValue)

@JvmName("massFlowRateFromWeightAndArea")
fun <
    WeightUnit : Weight,
    TimeUnit : Time,
    MassFlowRateUnit : MassFlowRate,
    Value : ScientificValue<PhysicalQuantity.MassFlowRate, MassFlowRateUnit>
    > MassFlowRateUnit.massFlowRate(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, MassFlowRateUnit) -> Value
) = byDividing(weight, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.massFlowRate

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.weight.mass
import com.splendo.kaluga.scientific.unit.ImperialMassFlowRate
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MassFlowRate
import com.splendo.kaluga.scientific.unit.MetricMassFlowRate
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.UKImperialMassFlowRate
import com.splendo.kaluga.scientific.unit.USCustomaryMassFlowRate
import kotlin.jvm.JvmName

@JvmName("metricMassFlowRateTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.MassFlowRate, MetricMassFlowRate>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.weight.mass(this, time)

@JvmName("imperialMassFlowRateTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.MassFlowRate, ImperialMassFlowRate>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.weight.mass(this, time)

@JvmName("ukImperialMassFlowRateTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.MassFlowRate, UKImperialMassFlowRate>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.weight.mass(this, time)

@JvmName("usCustomaryMassFlowRateTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.MassFlowRate, USCustomaryMassFlowRate>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.weight.mass(this, time)

@JvmName("massFlowRateTimesTime")
infix operator fun <MassFlowRateUnit : MassFlowRate, TimeUnit : Time> ScientificValue<PhysicalQuantity.MassFlowRate, MassFlowRateUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Kilogram.mass(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molality

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("molalityFromAmountOfSubstanceAndWeightDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    WeightUnit : Weight,
    MolalityUnit : Molality
    > MolalityUnit.molality(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = molality(amountOfSubstance, weight, ::DefaultScientificValue)

@JvmName("molalityFromAmountOfSubstanceAndWeight")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    WeightUnit : Weight,
    MolalityUnit : Molality,
    Value : ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
    > MolalityUnit.molality(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    factory: (Decimal, MolalityUnit) -> Value
) = byDividing(amountOfSubstance, weight, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molality

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byInverting
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarMass
import kotlin.jvm.JvmName

@JvmName("molalityFromInvertedMolarMassDefault")
fun <
    MolalityUnit : Molality,
    MolarMassUnit : MolarMass
    > MolalityUnit.molality(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = molality(molarMass, ::DefaultScientificValue)

@JvmName("molalityFromInvertedMolarMass")
fun <
    MolalityUnit : Molality,
    MolarMassUnit : MolarMass,
    Value : ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
    > MolalityUnit.molality(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    factory: (Decimal, MolalityUnit) -> Value
) = byInverting(molarMass, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molality

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.Molarity
import kotlin.jvm.JvmName

@JvmName("molalityFromMolarityAndDensityDefault")
fun <
    MolarityUnit : Molarity,
    DensityUnit : Density,
    MolalityUnit : Molality
    > MolalityUnit.molality(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>,
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = molality(molarity, density, ::DefaultScientificValue)

@JvmName("molalityFromMolarityAndDensity")
fun <
    MolarityUnit : Molarity,
    DensityUnit : Density,
    MolalityUnit : Molality,
    Value : ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
    > MolalityUnit.molality(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>,
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    factory: (Decimal, MolalityUnit) -> Value
) = byDividing(molarity, density, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molality

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("molalityFromMolarityAndSpecificVolumeDefault")
fun <
    MolarityUnit : Molarity,
    SpecificVolumeUnit : SpecificVolume,
    MolalityUnit : Molality
    > MolalityUnit.molality(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>,
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = molality(molarity, specificVolume, ::DefaultScientificValue)

@JvmName("molalityFromMolarityAndSpecificVolume")
fun <
    MolarityUnit : Molarity,
    SpecificVolumeUnit : SpecificVolume,
    MolalityUnit : Molality,
    Value : ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
    > MolalityUnit.molality(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>,
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    factory: (Decimal, MolalityUnit) -> Value
) = byMultiplying(molarity, specificVolume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molality

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarEnergy
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import kotlin.jvm.JvmName

@JvmName("molalityFromSpecificEnergyAndMolarEnergyDefault")
fun <
    SpecificEnergyUnit : SpecificEnergy,
    MolarEnergyUnit : MolarEnergy,
    MolalityUnit : Molality
    > MolalityUnit.molality(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>
) = molality(specificEnergy, molarEnergy, ::DefaultScientificValue)

@JvmName("molalityFromSpecificEnergyAndMolarEnergy")
fun <
    SpecificEnergyUnit : SpecificEnergy,
    MolarEnergyUnit : MolarEnergy,
    MolalityUnit : Molality,
    Value : ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
    > MolalityUnit.molality(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>,
    factory: (Decimal, MolalityUnit) -> Value
) = byDividing(specificEnergy, molarEnergy, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molality

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("molalityFromSpecificVolumeAndMolarVolumeDefault")
fun <
    SpecificVolumeUnit : SpecificVolume,
    MolarVolumeUnit : MolarVolume,
    MolalityUnit : Molality
    > MolalityUnit.molality(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
) = molality(specificVolume, molarVolume, ::DefaultScientificValue)

@JvmName("molalityFromSpecificVolumeAndMolarVolume")
fun <
    SpecificVolumeUnit : SpecificVolume,
    MolarVolumeUnit : MolarVolume,
    MolalityUnit : Molality,
    Value : ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
    > MolalityUnit.molality(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>,
    factory: (Decimal, MolalityUnit) -> Value
) = byDividing(specificVolume, molarVolume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molality

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.amountOfSubstance.amountOfSubstance
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("molalityTimesWeight")
infix operator fun <MolalityUnit : Molality, WeightUnit : Weight> ScientificValue<PhysicalQuantity.Molality, MolalityUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.amountOfSubstance.amountOfSubstance(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molality

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarMass.molarMass
import com.splendo.kaluga.scientific.unit.ImperialMolality
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricMolality
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.UKImperialMolality
import com.splendo.kaluga.scientific.unit.USCustomaryMolality
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricMolalityMolarMass")
fun ScientificValue<PhysicalQuantity.Molality, MetricMolality>.molarMass() =
    (unit.per per unit.amountOfSubstance).molarMass(this)

@JvmName("imperialMolalityMolarMass")
fun ScientificValue<PhysicalQuantity.Molality, ImperialMolality>.molarMass() =
    (unit.per per unit.amountOfSubstance).molarMass(this)

@JvmName("ukImperialMolalityMolarMass")
fun ScientificValue<PhysicalQuantity.Molality, UKImperialMolality>.molarMass() =
    (unit.per per unit.amountOfSubstance).molarMass(this)

@JvmName("usCustomaryMolalityMolarMass")
fun ScientificValue<PhysicalQuantity.Molality, USCustomaryMolality>.molarMass() =
    (unit.per per unit.amountOfSubstance).molarMass(this)

@JvmName("molalityMolarMass")
fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.Molality, MolalityUnit>.molarMass() =
    (Kilogram per unit.amountOfSubstance).molarMass(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molality

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarity.molarity
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("molalityTimesMetricDensity")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.Molality, MolalityUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, MetricDensity>
) = (unit.amountOfSubstance per density.unit.per).molarity(this, density)

@JvmName("molalityTimesImperialDensity")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.Molality, MolalityUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>
) = (unit.amountOfSubstance per density.unit.per).molarity(this, density)

@JvmName("molalityTimesUKImperialDensity")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.Molality, MolalityUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, UKImperialDensity>
) = (unit.amountOfSubstance per density.unit.per).molarity(this, density)

@JvmName("molalityTimesUSCustomaryDensity")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.Molality, MolalityUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>
) = (unit.amountOfSubstance per density.unit.per).molarity(this, density)

@JvmName("molalityTimesDensity")
infix operator fun <MolalityUnit : Molality, DensityUnit : Density> ScientificValue<PhysicalQuantity.Molality, MolalityUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = (unit.amountOfSubstance per CubicMeter).molarity(this, density)

@JvmName("molalityDivMetricSpecificVolume")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.Molality, MolalityUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>
) = (unit.amountOfSubstance per specificVolume.unit.volume).molarity(this, specificVolume)

@JvmName("molalityDivImperialSpecificVolume")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.Molality, MolalityUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = (unit.amountOfSubstance per specificVolume.unit.volume).molarity(this, specificVolume)

@JvmName("molalityDivUKImperialSpecificVolume")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.Molality, MolalityUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>
) = (unit.amountOfSubstance per specificVolume.unit.volume).molarity(this, specificVolume)

@JvmName("molarityDivUSCustomarySpecificVolume")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.Molality, MolalityUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>
) = (unit.amountOfSubstance per specificVolume.unit.volume).molarity(this, specificVolume)

@JvmName("molalityDivSpecificVolume")
infix operator fun <MolalityUnit : Molality, SpecificVolumeUnit : SpecificVolume> ScientificValue<PhysicalQuantity.Molality, MolalityUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = (unit.amountOfSubstance per CubicMeter).molarity(this, specificVolume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molality

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarEnergy.times
import com.splendo.kaluga.scientific.unit.ImperialMolality
import com.splendo.kaluga.scientific.unit.ImperialMolarEnergy
import com.splendo.kaluga.scientific.unit.MetricAndImperialMolarEnergy
import com.splendo.kaluga.scientific.unit.MetricMolality
import com.splendo.kaluga.scientific.unit.MetricMolarEnergy
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarEnergy
import com.splendo.kaluga.scientific.unit.UKImperialMolality
import com.splendo.kaluga.scientific.unit.USCustomaryMolality
import kotlin.jvm.JvmName

@JvmName("metricMolalityTimesMetricAndImperialMolarEnergy")
infix operator fun ScientificValue<PhysicalQuantity.Molality, MetricMolality>.times(molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>) =
    molarEnergy * this

@JvmName("imperialMolalityTimesMetricAndImperialMolarEnergy")
infix operator fun ScientificValue<PhysicalQuantity.Molality, ImperialMolality>.times(molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>) =
    molarEnergy * this

@JvmName("ukImperialMolalityTimesMetricAndImperialMolarEnergy")
infix operator fun ScientificValue<PhysicalQuantity.Molality, UKImperialMolality>.times(molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>) =
    molarEnergy * this

@JvmName("usCustomaryMolalityTimesMetricAndImperialMolarEnergy")
infix operator fun ScientificValue<PhysicalQuantity.Molality, USCustomaryMolality>.times(molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>) =
    molarEnergy * this

@JvmName("metricMolalityTimesMetricMolarEnergy")
infix operator fun ScientificValue<PhysicalQuantity.Molality, MetricMolality>.times(molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MetricMolarEnergy>) =
    molarEnergy * this

@JvmName("imperialMolalityTimesImperialMolarEnergy")
infix operator fun ScientificValue<PhysicalQuantity.Molality, ImperialMolality>.times(molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, ImperialMolarEnergy>) =
    molarEnergy * this

@JvmName("ukImperialMolalityTimesImperialMolarEnergy")
infix operator fun ScientificValue<PhysicalQuantity.Molality, UKImperialMolality>.times(molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, ImperialMolarEnergy>) =
    molarEnergy * this

@JvmName("usCustomaryMolalityTimesImperialMolarEnergy")
infix operator fun ScientificValue<PhysicalQuantity.Molality, USCustomaryMolality>.times(molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, ImperialMolarEnergy>) =
    molarEnergy * this

@JvmName("molalityTimesMolarEnergy")
infix operator fun <MolalityUnit : Molality, MolarEnergyUnit : MolarEnergy> ScientificValue<PhysicalQuantity.Molality, MolalityUnit>.times(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>
) = molarEnergy * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molality

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarVolume.times
import com.splendo.kaluga.scientific.converter.specificVolume.specificVolume
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialMolality
import com.splendo.kaluga.scientific.unit.ImperialMolarVolume
import com.splendo.kaluga.scientific.unit.ImperialMolarity
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricMolality
import com.splendo.kaluga.scientific.unit.MetricMolarVolume
import com.splendo.kaluga.scientific.unit.MetricMolarity
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.UKImperialMolality
import com.splendo.kaluga.scientific.unit.UKImperialMolarVolume
import com.splendo.kaluga.scientific.unit.UKImperialMolarity
import com.splendo.kaluga.scientific.unit.USCustomaryMolality
import com.splendo.kaluga.scientific.unit.USCustomaryMolarVolume
import com.splendo.kaluga.scientific.unit.USCustomaryMolarity
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricMolalityTimesMetricMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.Molality, MetricMolality>.times(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MetricMolarVolume>) =
    molarVolume * this

@JvmName("imperialMolalityTimesImperialMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.Molality, ImperialMolality>.times(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, ImperialMolarVolume>) =
    molarVolume * this

@JvmName("imperialMolalityTimesUKImperialMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.Molality, ImperialMolality>.times(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, UKImperialMolarVolume>) =
    molarVolume * this

@JvmName("imperialMolalityTimesUSCustomaryMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.Molality, ImperialMolality>.times(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, USCustomaryMolarVolume>) =
    molarVolume * this

@JvmName("ukImperialMolalityTimesImperialMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.Molality, UKImperialMolality>.times(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, ImperialMolarVolume>) =
    molarVolume * this

@JvmName("ukImperialMolalityTimesUKImperialMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.Molality, UKImperialMolality>.times(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, UKImperialMolarVolume>) =
    molarVolume * this

@JvmName("usCustomaryMolalityTimesImperialMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.Molality, USCustomaryMolality>.times(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, ImperialMolarVolume>) =
    molarVolume * this

@JvmName("usCustomaryMolalityTimesUSCustomaryMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.Molality, USCustomaryMolality>.times(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, USCustomaryMolarVolume>) =
    molarVolume * this

@JvmName("molalityTimesMolarVolume")
infix operator fun <MolalityUnit : Molality, MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.Molality, MolalityUnit>.times(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
) = molarVolume * this

@JvmName("metricMolalityDivMetricMolality")
infix operator fun ScientificValue<PhysicalQuantity.Molality, MetricMolality>.div(molarity: ScientificValue<PhysicalQuantity.Molarity, MetricMolarity>) =
    (molarity.unit.per per unit.per).specificVolume(this, molarity)

@JvmName("imperialMolalityDivImperialMolarity")
infix operator fun ScientificValue<PhysicalQuantity.Molality, ImperialMolality>.div(molarity: ScientificValue<PhysicalQuantity.Molarity, ImperialMolarity>) =
    (molarity.unit.per per unit.per).specificVolume(this, molarity)

@JvmName("imperialMolalityDivUKImperialMolarity")
infix operator fun ScientificValue<PhysicalQuantity.Molality, ImperialMolality>.div(molarity: ScientificValue<PhysicalQuantity.Molarity, UKImperialMolarity>) =
    (molarity.unit.per per unit.per).specificVolume(this, molarity)

@JvmName("imperialMolalityDivUSCustomaryMolarity")
infix operator fun ScientificValue<PhysicalQuantity.Molality, ImperialMolality>.div(molarity: ScientificValue<PhysicalQuantity.Molarity, USCustomaryMolarity>) =
    (molarity.unit.per per unit.per).specificVolume(this, molarity)

@JvmName("ukImperialMolalityDivImperialMolarity")
infix operator fun ScientificValue<PhysicalQuantity.Molality, UKImperialMolality>.div(molarity: ScientificValue<PhysicalQuantity.Molarity, ImperialMolarity>) =
    (molarity.unit.per per unit.per).specificVolume(this, molarity)

@JvmName("ukImperialMolalityDivUKImperialMolarity")
infix operator fun ScientificValue<PhysicalQuantity.Molality, UKImperialMolality>.div(molarity: ScientificValue<PhysicalQuantity.Molarity, UKImperialMolarity>) =
    (molarity.unit.per per unit.per).specificVolume(this, molarity)

@JvmName("usCustomaryMolalityDivImperialMolarity")
infix operator fun ScientificValue<PhysicalQuantity.Molality, USCustomaryMolality>.div(molarity: ScientificValue<PhysicalQuantity.Molarity, ImperialMolarity>) =
    (molarity.unit.per per unit.per).specificVolume(this, molarity)

@JvmName("usCustomaryMolalityDivUSCustomaryMolarity")
infix operator fun ScientificValue<PhysicalQuantity.Molality, USCustomaryMolality>.div(molarity: ScientificValue<PhysicalQuantity.Molarity, USCustomaryMolarity>) =
    (molarity.unit.per per unit.per).specificVolume(this, molarity)

@JvmName("molalityDivMolarity")
infix operator fun <MolalityUnit : Molality, MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.Molality, MolalityUnit>.div(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = (CubicMeter per Kilogram).specificVolume(this, molarity)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarEnergy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.MolarEnergy
import kotlin.jvm.JvmName

@JvmName("molarEnergyFromEnergyAndAmountOfSubstanceDefault")
fun <
    EnergyUnit : Energy,
    AmountOfSubstanceUnit : AmountOfSubstance,
    MolarEnergyUnit : MolarEnergy
    > MolarEnergyUnit.molarEnergy(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = molarEnergy(energy, amountOfSubstance, ::DefaultScientificValue)

@JvmName("molarEnergyFromEnergyAndAmountOfSubstance")
fun <
    EnergyUnit : Energy,
    AmountOfSubstanceUnit : AmountOfSubstance,
    MolarEnergyUnit : MolarEnergy,
    Value : ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>
    > MolarEnergyUnit.molarEnergy(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    factory: (Decimal, MolarEnergyUnit) -> Value
) = byDividing(energy, amountOfSubstance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarEnergy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarEnergy
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import kotlin.jvm.JvmName

@JvmName("molarEnergyFromSpecificEnergyAndMolalityDefault")
fun <
    SpecificEnergyUnit : SpecificEnergy,
    MolalityUnit : Molality,
    MolarEnergyUnit : MolarEnergy
    > MolarEnergyUnit.molarEnergy(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = molarEnergy(specificEnergy, molality, ::DefaultScientificValue)

@JvmName("molarEnergyFromSpecificEnergyAndMolality")
fun <
    SpecificEnergyUnit : SpecificEnergy,
    MolalityUnit : Molality,
    MolarEnergyUnit : MolarEnergy,
    Value : ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>
    > MolarEnergyUnit.molarEnergy(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>,
    factory: (Decimal, MolarEnergyUnit) -> Value
) = byDividing(specificEnergy, molality, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarEnergy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.MolarEnergy
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import kotlin.jvm.JvmName

@JvmName("molarEnergyFromMolarMassAndSpecificEnergyDefault")
fun <
    MolarEnergyUnit : MolarEnergy,
    MolarMassUnit : MolarMass,
    SpecificEnergyUnit : SpecificEnergy
    > MolarEnergyUnit.molarEnergy(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = molarEnergy(specificEnergy, molarMass, ::DefaultScientificValue)

@JvmName("molarEnergyFromMolarMassAndSpecificEnergy")
fun <
    MolarEnergyUnit : MolarEnergy,
    MolarMassUnit : MolarMass,
    SpecificEnergyUnit : SpecificEnergy,
    Value : ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>
    > MolarEnergyUnit.molarEnergy(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    factory: (Decimal, MolarEnergyUnit) -> Value
) = byMultiplying(specificEnergy, molarMass, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarEnergy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.energy.energy
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.ImperialMolarEnergy
import com.splendo.kaluga.scientific.unit.MetricAndImperialMolarEnergy
import com.splendo.kaluga.scientific.unit.MetricMolarEnergy
import com.splendo.kaluga.scientific.unit.MolarEnergy
import kotlin.jvm.JvmName

@JvmName("metricAndImperialMolarEnergyTimesAmountOfSubstance")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>.times(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (unit.energy).energy(this, amountOfSubstance)

@JvmName("metricMolarEnergyTimesAmountOfSubstance")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.MolarEnergy, MetricMolarEnergy>.times(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (unit.energy).energy(this, amountOfSubstance)

@JvmName("imperialMolarEnergyTimesAmountOfSubstance")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.MolarEnergy, ImperialMolarEnergy>.times(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (unit.energy).energy(this, amountOfSubstance)

@JvmName("molarEnergyTimesAmountOfSubstance")
infix operator fun <MolarEnergyUnit : MolarEnergy, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>.times(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (unit.energy).energy(this, amountOfSubstance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarEnergy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarMass.molarMass
import com.splendo.kaluga.scientific.unit.ImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricAndImperialMolarEnergy
import com.splendo.kaluga.scientific.unit.MetricSpecificEnergy
import com.splendo.kaluga.scientific.unit.MolarEnergy
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.UKImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.USCustomarySpecificEnergy
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricAndImperialMolarEnergyDivMetricSpecificEnergy")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>.div(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, MetricSpecificEnergy>
) = (specificEnergy.unit.per per unit.per).molarMass(this, specificEnergy)

@JvmName("metricAndImperialMolarEnergyDivImperialSpecificEnergy")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>.div(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>
) = (specificEnergy.unit.per per unit.per).molarMass(this, specificEnergy)

@JvmName("metricAndImperialMolarEnergyDivUKImperialSpecificEnergy")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>.div(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, UKImperialSpecificEnergy>
) = (specificEnergy.unit.per per unit.per).molarMass(this, specificEnergy)

@JvmName("metricAndImperialMolarEnergyDivUSCustomarySpecificEnergy")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>.div(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, USCustomarySpecificEnergy>
) = (specificEnergy.unit.per per unit.per).molarMass(this, specificEnergy)

@JvmName("molarEnergyDivMetricSpecificEnergy")
infix operator fun <MolarEnergyUnit : MolarEnergy> ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>.div(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, MetricSpecificEnergy>
) = (specificEnergy.unit.per per unit.per).molarMass(this, specificEnergy)

@JvmName("molarEnergyDivImperialSpecificEnergy")
infix operator fun <MolarEnergyUnit : MolarEnergy> ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>.div(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>
) = (specificEnergy.unit.per per unit.per).molarMass(this, specificEnergy)

@JvmName("molarEnergyDivUKImperialSpecificEnergy")
infix operator fun <MolarEnergyUnit : MolarEnergy> ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>.div(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, UKImperialSpecificEnergy>
) = (specificEnergy.unit.per per unit.per).molarMass(this, specificEnergy)

@JvmName("molarEnergyDivUSCustomarySpecificEnergy")
infix operator fun <MolarEnergyUnit : MolarEnergy> ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>.div(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, USCustomarySpecificEnergy>
) = (specificEnergy.unit.per per unit.per).molarMass(this, specificEnergy)

@JvmName("molarEnergyDivSpecificEnergy")
infix operator fun <MolarEnergyUnit : MolarEnergy, SpecificEnergyUnit : SpecificEnergy> ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>.div(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>
) = (Kilogram per unit.per).molarMass(this, specificEnergy)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarEnergy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.specificEnergy.specificEnergy
import com.splendo.kaluga.scientific.unit.ImperialMolality
import com.splendo.kaluga.scientific.unit.ImperialMolarEnergy
import com.splendo.kaluga.scientific.unit.ImperialMolarMass
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricAndImperialMolarEnergy
import com.splendo.kaluga.scientific.unit.MetricMolality
import com.splendo.kaluga.scientific.unit.MetricMolarEnergy
import com.splendo.kaluga.scientific.unit.MetricMolarMass
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarEnergy
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.UKImperialMolality
import com.splendo.kaluga.scientific.unit.UKImperialMolarMass
import com.splendo.kaluga.scientific.unit.USCustomaryMolality
import com.splendo.kaluga.scientific.unit.USCustomaryMolarMass
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricAndImperialMolarEnergyDivMetricMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>.div(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MetricMolarMass>
) = (unit.energy per molarMass.unit.weight).specificEnergy(this, molarMass)

@JvmName("metricAndImperialMolarEnergyDivImperialMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>.div(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>
) = (unit.energy per molarMass.unit.weight).specificEnergy(this, molarMass)

@JvmName("metricAndImperialMolarEnergyDivUKImperialMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>.div(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, UKImperialMolarMass>
) = (unit.energy per molarMass.unit.weight).specificEnergy(this, molarMass)

@JvmName("metricAndImperialMolarEnergyDivUSCustomaryMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>.div(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, USCustomaryMolarMass>
) = (unit.energy per molarMass.unit.weight).specificEnergy(this, molarMass)

@JvmName("metricMolarEnergyDivMetricMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, MetricMolarEnergy>.div(molarMass: ScientificValue<PhysicalQuantity.MolarMass, MetricMolarMass>) =
    (unit.energy per molarMass.unit.weight).specificEnergy(this, molarMass)

@JvmName("imperialMolarEnergyDivImperialMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, ImperialMolarEnergy>.div(molarMass: ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>) =
    (unit.energy per molarMass.unit.weight).specificEnergy(this, molarMass)

@JvmName("imperialMolarEnergyDivUKImperialMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, ImperialMolarEnergy>.div(molarMass: ScientificValue<PhysicalQuantity.MolarMass, UKImperialMolarMass>) =
    (unit.energy per molarMass.unit.weight).specificEnergy(this, molarMass)

@JvmName("imperialMolarEnergyDivUSCustomaryMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, ImperialMolarEnergy>.div(molarMass: ScientificValue<PhysicalQuantity.MolarMass, USCustomaryMolarMass>) =
    (unit.energy per molarMass.unit.weight).specificEnergy(this, molarMass)

@JvmName("molarEnergyDivMolarMass")
infix operator fun <MolarEnergyUnit : MolarEnergy, MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>.div(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = (Joule per Kilogram).specificEnergy(this, molarMass)

@JvmName("metricAndImperialMolarEnergyTimesMetricMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, MetricMolality>
) = (unit.energy per molality.unit.per).specificEnergy(this, molality)

@JvmName("metricAndImperialMolarEnergyTimesImperialMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, ImperialMolality>
) = (unit.energy per molality.unit.per).specificEnergy(this, molality)

@JvmName("metricAndImperialMolarEnergyTimesUKImperialMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, UKImperialMolality>
) = (unit.energy per molality.unit.per).specificEnergy(this, molality)

@JvmName("metricAndImperialMolarEnergyTimesUSCustomaryMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, MetricAndImperialMolarEnergy>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, USCustomaryMolality>
) = (unit.energy per molality.unit.per).specificEnergy(this, molality)

@JvmName("metricMolarEnergyTimesMetricMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, MetricMolarEnergy>.times(molality: ScientificValue<PhysicalQuantity.Molality, MetricMolality>) =
    (unit.energy per molality.unit.per).specificEnergy(this, molality)

@JvmName("imperialMolarEnergyTimesImperialMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, ImperialMolarEnergy>.times(molality: ScientificValue<PhysicalQuantity.Molality, ImperialMolality>) =
    (unit.energy per molality.unit.per).specificEnergy(this, molality)

@JvmName("imperialMolarEnergyTimesUKImperialMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, ImperialMolarEnergy>.times(molality: ScientificValue<PhysicalQuantity.Molality, UKImperialMolality>) =
    (unit.energy per molality.unit.per).specificEnergy(this, molality)

@JvmName("imperialMolarEnergyTimesUSCustomaryMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarEnergy, ImperialMolarEnergy>.times(molality: ScientificValue<PhysicalQuantity.Molality, USCustomaryMolality>) =
    (unit.energy per molality.unit.per).specificEnergy(this, molality)

@JvmName("molarEnergyTimesMolality")
infix operator fun <MolarEnergyUnit : MolarEnergy, MolalityUnit : Molality> ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = (Joule per Kilogram).specificEnergy(this, molality)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarMass

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.MolarVolume
import kotlin.jvm.JvmName

@JvmName("molarMassFromDensityAndMolarVolumeDefault")
fun <
    MolarMassUnit : MolarMass,
    DensityUnit : Density,
    MolarVolumeUnit : MolarVolume
    > MolarMassUnit.molarMass(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>,
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = molarMass(molarVolume, density, ::DefaultScientificValue)

@JvmName("molarMassFromDensityAndMolarVolume")
fun <
    MolarMassUnit : MolarMass,
    DensityUnit : Density,
    MolarVolumeUnit : MolarVolume,
    Value : ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
    > MolarMassUnit.molarMass(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>,
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    factory: (Decimal, MolarMassUnit) -> Value
) = byMultiplying(molarVolume, density, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarMass

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.Molarity
import kotlin.jvm.JvmName

@JvmName("molarMassFromDensityAndMolarityDefault")
fun <
    DensityUnit : Density,
    MolarityUnit : Molarity,
    MolarMassUnit : MolarMass
    > MolarMassUnit.molarMass(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = molarMass(density, molarity, ::DefaultScientificValue)

@JvmName("molarMassFromDensityAndMolarity")
fun <
    DensityUnit : Density,
    MolarityUnit : Molarity,
    MolarMassUnit : MolarMass,
    Value : ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
    > MolarMassUnit.molarMass(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>,
    factory: (Decimal, MolarMassUnit) -> Value
) = byDividing(density, molarity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarMass

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byInverting
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarMass
import kotlin.jvm.JvmName

@JvmName("molarMassFromInvertedMolalityDefault")
fun <
    MolalityUnit : Molality,
    MolarMassUnit : MolarMass
    > MolarMassUnit.molarMass(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = molarMass(molality, ::DefaultScientificValue)

@JvmName("molarMassFromInvertedMolality")
fun <
    MolalityUnit : Molality,
    MolarMassUnit : MolarMass,
    Value : ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
    > MolarMassUnit.molarMass(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>,
    factory: (Decimal, MolarMassUnit) -> Value
) = byInverting(molality, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarMass

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.MolarEnergy
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import kotlin.jvm.JvmName

@JvmName("molarMassFromMolarEnergyAndSpecificEnergyDefault")
fun <
    MolarEnergyUnit : MolarEnergy,
    SpecificEnergyUnit : SpecificEnergy,
    MolarMassUnit : MolarMass
    > MolarMassUnit.molarMass(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>,
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>
) = molarMass(molarEnergy, specificEnergy, ::DefaultScientificValue)

@JvmName("molarMassFromMolarEnergyAndSpecificEnergy")
fun <
    MolarEnergyUnit : MolarEnergy,
    SpecificEnergyUnit : SpecificEnergy,
    MolarMassUnit : MolarMass,
    Value : ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
    > MolarMassUnit.molarMass(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>,
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    factory: (Decimal, MolarMassUnit) -> Value
) = byDividing(molarEnergy, specificEnergy, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarMass

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("molarMassFromMolarVolumeAndSpecificVolumeDefault")
fun <
    MolarVolumeUnit : MolarVolume,
    SpecificVolumeUnit : SpecificVolume,
    MolarMassUnit : MolarMass
    > MolarMassUnit.molarMass(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>,
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = molarMass(molarVolume, specificVolume, ::DefaultScientificValue)

@JvmName("molarMassFromMolarVolumeAndSpecificVolume")
fun <
    MolarVolumeUnit : MolarVolume,
    SpecificVolumeUnit : SpecificVolume,
    MolarMassUnit : MolarMass,
    Value : ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
    > MolarMassUnit.molarMass(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>,
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    factory: (Decimal, MolarMassUnit) -> Value
) = byDividing(molarVolume, specificVolume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarMass

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("molarMassFromWeightAndAmountOfSubstanceDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    WeightUnit : Weight,
    MolarMassUnit : MolarMass
    > MolarMassUnit.molarMass(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = molarMass(weight, amountOfSubstance, ::DefaultScientificValue)

@JvmName("molarMassFromWeightAndAmountOfSubstance")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    WeightUnit : Weight,
    MolarMassUnit : MolarMass,
    Value : ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
    > MolarMassUnit.molarMass(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    factory: (Decimal, MolarMassUnit) -> Value
) = byDividing(weight, amountOfSubstance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarMass

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.density.density
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialMolarMass
import com.splendo.kaluga.scientific.unit.ImperialMolarVolume
import com.splendo.kaluga.scientific.unit.ImperialMolarity
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricMolarMass
import com.splendo.kaluga.scientific.unit.MetricMolarVolume
import com.splendo.kaluga.scientific.unit.MetricMolarity
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.UKImperialMolarMass
import com.splendo.kaluga.scientific.unit.UKImperialMolarVolume
import com.splendo.kaluga.scientific.unit.UKImperialMolarity
import com.splendo.kaluga.scientific.unit.USCustomaryMolarMass
import com.splendo.kaluga.scientific.unit.USCustomaryMolarVolume
import com.splendo.kaluga.scientific.unit.USCustomaryMolarity
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricMolarMassTimesMetricMolarity")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, MetricMolarMass>.times(molarity: ScientificValue<PhysicalQuantity.Molarity, MetricMolarity>) =
    (unit.weight per molarity.unit.per).density(this, molarity)

@JvmName("imperialMolarMassTimesImperialMolarity")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>.times(molarity: ScientificValue<PhysicalQuantity.Molarity, ImperialMolarity>) =
    (unit.weight per molarity.unit.per).density(this, molarity)

@JvmName("imperialMolarMassTimesUKImperialMolarity")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>.times(molarity: ScientificValue<PhysicalQuantity.Molarity, UKImperialMolarity>) =
    (unit.weight per molarity.unit.per).density(this, molarity)

@JvmName("imperialMolarMassTimesUSCustomaryMolarity")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>.times(molarity: ScientificValue<PhysicalQuantity.Molarity, USCustomaryMolarity>) =
    (unit.weight per molarity.unit.per).density(this, molarity)

@JvmName("ukImperialMolarMassTimesImperialMolarity")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, UKImperialMolarMass>.times(molarity: ScientificValue<PhysicalQuantity.Molarity, ImperialMolarity>) =
    (unit.weight per molarity.unit.per).density(this, molarity)

@JvmName("ukImperialMolarMassTimesUKImperialMolarity")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, UKImperialMolarMass>.times(molarity: ScientificValue<PhysicalQuantity.Molarity, UKImperialMolarity>) =
    (unit.weight per molarity.unit.per).density(this, molarity)

@JvmName("usCustomaryMolarMassTimesImperialMolarity")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, USCustomaryMolarMass>.times(molarity: ScientificValue<PhysicalQuantity.Molarity, ImperialMolarity>) =
    (unit.weight per molarity.unit.per).density(this, molarity)

@JvmName("usCustomaryMolarMassTimesUSCustomaryMolarity")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, USCustomaryMolarMass>.times(molarity: ScientificValue<PhysicalQuantity.Molarity, USCustomaryMolarity>) =
    (unit.weight per molarity.unit.per).density(this, molarity)

@JvmName("molarMassTimesMolarity")
infix operator fun <MolarMassUnit : MolarMass, MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.times(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = (Kilogram per CubicMeter).density(this, molarity)

@JvmName("metricMolarMassDivMetricMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, MetricMolarMass>.div(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MetricMolarVolume>) =
    (unit.weight per molarVolume.unit.volume).density(this, molarVolume)

@JvmName("imperialMolarMassDivImperialMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>.div(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, ImperialMolarVolume>) =
    (unit.weight per molarVolume.unit.volume).density(this, molarVolume)

@JvmName("imperialMolarMassDivUKImperialMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>.div(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, UKImperialMolarVolume>) =
    (unit.weight per molarVolume.unit.volume).density(this, molarVolume)

@JvmName("imperialMolarMassDivUSCustomaryMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>.div(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, USCustomaryMolarVolume>) =
    (unit.weight per molarVolume.unit.volume).density(this, molarVolume)

@JvmName("ukImperialMolarMassDivImperialMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, UKImperialMolarMass>.div(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, ImperialMolarVolume>) =
    (unit.weight per molarVolume.unit.volume).density(this, molarVolume)

@JvmName("ukImperialMolarMassDivUKImperialMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, UKImperialMolarMass>.div(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, UKImperialMolarVolume>) =
    (unit.weight per molarVolume.unit.volume).density(this, molarVolume)

@JvmName("usCustomaryMolarMassDivImperialMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, USCustomaryMolarMass>.div(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, ImperialMolarVolume>) =
    (unit.weight per molarVolume.unit.volume).density(this, molarVolume)

@JvmName("usCustomaryMolarMassDivUSCustomaryMolarVolume")
infix operator fun ScientificValue<PhysicalQuantity.MolarMass, USCustomaryMolarMass>.div(molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, USCustomaryMolarVolume>) =
    (unit.weight per molarVolume.unit.volume).density(this, molarVolume)

@JvmName("molarMassDivMolarVolume")
infix operator fun <MolarMassUnit : MolarMass, MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.div(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
) = (Kilogram per CubicMeter).density(this, molarVolume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarMass

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molality.molality
import com.splendo.kaluga.scientific.unit.ImperialMolarMass
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricMolarMass
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.UKImperialMolarMass
import com.splendo.kaluga.scientific.unit.USCustomaryMolarMass
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricMolarMassMolality")
fun ScientificValue<PhysicalQuantity.MolarMass, MetricMolarMass>.molality() =
    (unit.per per unit.weight).molality(this)

@JvmName("imperialMolarMassMolality")
fun ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>.molality() =
    (unit.per per unit.weight).molality(this)

@JvmName("ukImperialMolarMassMolality")
fun ScientificValue<PhysicalQuantity.MolarMass, UKImperialMolarMass>.molality() =
    (unit.per per unit.weight).molality(this)

@JvmName("usCustomaryMolarMassMolality")
fun ScientificValue<PhysicalQuantity.MolarMass, USCustomaryMolarMass>.molality() =
    (unit.per per unit.weight).molality(this)

@JvmName("molarMassMolality")
fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.molality() =
    (unit.per per Kilogram).molality(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarMass

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.specificEnergy.times
import com.splendo.kaluga.scientific.unit.ImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.MetricSpecificEnergy
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.UKImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.USCustomarySpecificEnergy
import kotlin.jvm.JvmName

@JvmName("molarMassTimesMetricSpecificEnergy")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.times(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, MetricSpecificEnergy>
) = specificEnergy * this

@JvmName("molarMassTimesImperialSpecificEnergy")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.times(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>
) = specificEnergy * this

@JvmName("molarMassTimesUKImperialSpecificEnergy")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.times(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, UKImperialSpecificEnergy>
) = specificEnergy * this

@JvmName("molarMassTimesUSCustomarySpecificEnergy")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.times(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, USCustomarySpecificEnergy>
) = specificEnergy * this

@JvmName("molarMassTimesSpecificEnergy")
infix operator fun <SpecificEnergyUnit : SpecificEnergy, MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.times(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>
) = specificEnergy * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarMass

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarVolume.molarVolume
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("molarMassDivMetricDensity")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, MetricDensity>
) = (density.unit.per per unit.per).molarVolume(this, density)

@JvmName("molarMassDivImperialDensity")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>
) = (density.unit.per per unit.per).molarVolume(this, density)

@JvmName("molarMassDivUKImperialDensity")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, UKImperialDensity>
) = (density.unit.per per unit.per).molarVolume(this, density)

@JvmName("molarMassDivUSCustomaryDensity")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>
) = (density.unit.per per unit.per).molarVolume(this, density)

@JvmName("molarMassDivDensity")
infix operator fun <MolarMassUnit : MolarMass, DensityUnit : Density> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = (CubicMeter per unit.per).molarVolume(this, density)

@JvmName("molarMassTimesMetricSpecificVolume")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.times(specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>) =
    (specificVolume.unit.volume per unit.per).molarVolume(this, specificVolume)

@JvmName("molarMassTimesImperialSpecificVolume")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = (specificVolume.unit.volume per unit.per).molarVolume(this, specificVolume)

@JvmName("molarMassTimesUKImperialSpecificVolume")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>
) = (specificVolume.unit.volume per unit.per).molarVolume(this, specificVolume)

@JvmName("molarMassTimesUSCustomarySpecificVolume")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>
) = (specificVolume.unit.volume per unit.per).molarVolume(this, specificVolume)

@JvmName("molarMassTimesSpecificVolume")
infix operator fun <MolarMassUnit : MolarMass, SpecificVolumeUnit : SpecificVolume> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = (CubicMeter per unit.per).molarVolume(this, specificVolume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarMass

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.weight.mass
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.ImperialMolarMass
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricMolarMass
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.UKImperialMolarMass
import com.splendo.kaluga.scientific.unit.USCustomaryMolarMass
import kotlin.jvm.JvmName

@JvmName("metricMolarMassTimesAmountOfSubstance")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.MolarMass, MetricMolarMass>.times(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = unit.weight.mass(this, amountOfSubstance)

@JvmName("imperialMolarMassTimesAmountOfSubstance")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>.times(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = unit.weight.mass(this, amountOfSubstance)

@JvmName("ukImperialMolarMassTimesAmountOfSubstance")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.MolarMass, UKImperialMolarMass>.times(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = unit.weight.mass(this, amountOfSubstance)

@JvmName("usCustomaryMolarMassTimesAmountOfSubstance")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.MolarMass, USCustomaryMolarMass>.times(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = unit.weight.mass(this, amountOfSubstance)

@JvmName("molarMassTimesAmountOfSubstance")
infix operator fun <MolarMassUnit : MolarMass, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>.times(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = Kilogram.mass(this, amountOfSubstance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarVolume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.MolarVolume
import kotlin.jvm.JvmName

@JvmName("molarVolumeFromMolarMassAndDensityDefault")
fun <
    MolarMassUnit : MolarMass,
    DensityUnit : Density,
    MolarVolumeUnit : MolarVolume
    > MolarVolumeUnit.molarVolume(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = molarVolume(molarMass, density, ::DefaultScientificValue)

@JvmName("molarVolumeFromMolarMassAndDensity")
fun <
    MolarMassUnit : MolarMass,
    DensityUnit : Density,
    MolarVolumeUnit : MolarVolume,
    Value : ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
    > MolarVolumeUnit.molarVolume(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    factory: (Decimal, MolarVolumeUnit) -> Value
) = byDividing(molarMass, density, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarVolume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byInverting
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.Molarity
import kotlin.jvm.JvmName

@JvmName("molarVolumeFromInvertedMolarityDefault")
fun <
    MolarityUnit : Molarity,
    MolarVolumeUnit : MolarVolume,
    > MolarVolumeUnit.molarVolume(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = molarVolume(molarity, ::DefaultScientificValue)

@JvmName("molarVolumeFromInvertedMolarity")
fun <
    MolarityUnit : Molarity,
    MolarVolumeUnit : MolarVolume,
    Value : ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
    > MolarVolumeUnit.molarVolume(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>,
    factory: (Decimal, MolarVolumeUnit) -> Value
) = byInverting(molarity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarVolume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("molarVolumeFromSpecificVolumeAndMolalityDefault")
fun <
    SpecificVolumeUnit : SpecificVolume,
    MolalityUnit : Molality,
    MolarVolumeUnit : MolarVolume
    > MolarVolumeUnit.molarVolume(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = molarVolume(specificVolume, molality, ::DefaultScientificValue)

@JvmName("molarVolumeFromSpecificVolumeAndMolality")
fun <
    SpecificVolumeUnit : SpecificVolume,
    MolalityUnit : Molality,
    MolarVolumeUnit : MolarVolume,
    Value : ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
    > MolarVolumeUnit.molarVolume(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>,
    factory: (Decimal, MolarVolumeUnit) -> Value
) = byDividing(specificVolume, molality, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarVolume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("molarVolumeFromSpecificVolumeAndMolarMassDefault")
fun <
    MolarVolumeUnit : MolarVolume,
    SpecificVolumeUnit : SpecificVolume,
    MolarMassUnit : MolarMass
    > MolarVolumeUnit.molarVolume(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = molarVolume(molarMass, specificVolume, ::DefaultScientificValue)

@JvmName("molarVolumeFromSpecificVolumeAndMolarMass")
fun <
    MolarVolumeUnit : MolarVolume,
    SpecificVolumeUnit : SpecificVolume,
    MolarMassUnit : MolarMass,
    Value : ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
    > MolarVolumeUnit.molarVolume(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    factory: (Decimal, MolarVolumeUnit) -> Value
) = byMultiplying(molarMass, specificVolume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarVolume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("molarVolumeFromVolumeAndAmountOfSubstanceDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    VolumeUnit : Volume,
    MolarVolumeUnit : MolarVolume
    > MolarVolumeUnit.molarVolume(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = molarVolume(volume, amountOfSubstance, ::DefaultScientificValue)

@JvmName("molarVolumeFromVolumeAndAmountOfSubstance")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    VolumeUnit : Volume,
    MolarVolumeUnit : MolarVolume,
    Value : ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
    > MolarVolumeUnit.molarVolume(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    factory: (Decimal, MolarVolumeUnit) -> Value
) = byDividing(volume, amountOfSubstance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarVolume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarMass.molarMass
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("molarVolumeTimesMetricDensity")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>.times(density: ScientificValue<PhysicalQuantity.Density, MetricDensity>) =
    (density.unit.weight per unit.per).molarMass(this, density)

@JvmName("molarVolumeTimesImperialDensity")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>.times(density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>) =
    (density.unit.weight per unit.per).molarMass(this, density)

@JvmName("molarVolumeTimesUKImperialDensity")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>.times(density: ScientificValue<PhysicalQuantity.Density, UKImperialDensity>) =
    (density.unit.weight per unit.per).molarMass(this, density)

@JvmName("molarVolumeTimesUSCustomaryDensity")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>.times(density: ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>) =
    (density.unit.weight per unit.per).molarMass(this, density)

@JvmName("molarVolumeTimesDensity")
infix operator fun <MolarVolumeUnit : MolarVolume, DensityUnit : Density> ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = (Kilogram per unit.per).molarMass(this, density)

@JvmName("molarVolumeDivMetricSpecificVolume")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>
) = (specificVolume.unit.per per unit.per).molarMass(this, specificVolume)

@JvmName("molarVolumeDivImperialSpecificVolume")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = (specificVolume.unit.per per unit.per).molarMass(this, specificVolume)

@JvmName("molarVolumeDivUKImperialSpecificVolume")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>
) = (specificVolume.unit.per per unit.per).molarMass(this, specificVolume)

@JvmName("molarVolumeDivUSCustomarySpecificVolume")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>
) = (specificVolume.unit.per per unit.per).molarMass(this, specificVolume)

@JvmName("molarVolumeDivSpecificVolume")
infix operator fun <MolarVolumeUnit : MolarVolume, SpecificVolumeUnit : SpecificVolume> ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = (Kilogram per unit.per).molarMass(this, specificVolume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarVolume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarity.molarity
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialMolarVolume
import com.splendo.kaluga.scientific.unit.MetricMolarVolume
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.UKImperialMolarVolume
import com.splendo.kaluga.scientific.unit.USCustomaryMolarVolume
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricMolarVolumeMolarity")
fun ScientificValue<PhysicalQuantity.MolarVolume, MetricMolarVolume>.molarity() =
    (unit.per per unit.volume).molarity(this)

@JvmName("imperialMolarVolumeMolarity")
fun ScientificValue<PhysicalQuantity.MolarVolume, ImperialMolarVolume>.molarity() =
    (unit.per per unit.volume).molarity(this)

@JvmName("ukImperialMolarVolumeMolarity")
fun ScientificValue<PhysicalQuantity.MolarVolume, UKImperialMolarVolume>.molarity() =
    (unit.per per unit.volume).molarity(this)

@JvmName("usCustomaryMolarVolumeMolarity")
fun ScientificValue<PhysicalQuantity.MolarVolume, USCustomaryMolarVolume>.molarity() =
    (unit.per per unit.volume).molarity(this)

@JvmName("molarVolumeMolarity")
fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>.molarity() =
    (unit.per per CubicMeter).molarity(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarVolume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.specificVolume.specificVolume
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialMolality
import com.splendo.kaluga.scientific.unit.ImperialMolarMass
import com.splendo.kaluga.scientific.unit.ImperialMolarVolume
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricMolality
import com.splendo.kaluga.scientific.unit.MetricMolarMass
import com.splendo.kaluga.scientific.unit.MetricMolarVolume
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.UKImperialMolality
import com.splendo.kaluga.scientific.unit.UKImperialMolarMass
import com.splendo.kaluga.scientific.unit.UKImperialMolarVolume
import com.splendo.kaluga.scientific.unit.USCustomaryMolality
import com.splendo.kaluga.scientific.unit.USCustomaryMolarMass
import com.splendo.kaluga.scientific.unit.USCustomaryMolarVolume
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricMolarVolumeTimesMetricMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, MetricMolarVolume>.times(molality: ScientificValue<PhysicalQuantity.Molality, MetricMolality>) =
    (unit.volume per molality.unit.per).specificVolume(this, molality)

@JvmName("imperialMolarVolumeTimesImperialMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, ImperialMolarVolume>.times(molality: ScientificValue<PhysicalQuantity.Molality, ImperialMolality>) =
    (unit.volume per molality.unit.per).specificVolume(this, molality)

@JvmName("imperialMolarVolumeTimesUKImperialMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, ImperialMolarVolume>.times(molality: ScientificValue<PhysicalQuantity.Molality, UKImperialMolality>) =
    (unit.volume per molality.unit.per).specificVolume(this, molality)

@JvmName("imperialMolarVolumeTimesUSCustomaryMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, ImperialMolarVolume>.times(molality: ScientificValue<PhysicalQuantity.Molality, USCustomaryMolality>) =
    (unit.volume per molality.unit.per).specificVolume(this, molality)

@JvmName("ukImperialMolarVolumeTimesImperialMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, UKImperialMolarVolume>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, ImperialMolality>
) = (unit.volume per molality.unit.per).specificVolume(this, molality)

@JvmName("ukImperialMolarVolumeTimesUKImperialMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, UKImperialMolarVolume>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, UKImperialMolality>
) = (unit.volume per molality.unit.per).specificVolume(this, molality)

@JvmName("usCustomaryMolarVolumeTimesImperialMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, USCustomaryMolarVolume>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, ImperialMolality>
) = (unit.volume per molality.unit.per).specificVolume(this, molality)

@JvmName("usCustomaryMolarVolumeTimesUSCustomaryMolality")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, USCustomaryMolarVolume>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, USCustomaryMolality>
) = (unit.volume per molality.unit.per).specificVolume(this, molality)

@JvmName("molarVolumeTimesMolality")
infix operator fun <MolarVolumeUnit : MolarVolume, MolalityUnit : Molality> ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = (CubicMeter per Kilogram).specificVolume(this, molality)

@JvmName("metricMolarVolumeDivMetricMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, MetricMolarVolume>.div(molarMass: ScientificValue<PhysicalQuantity.MolarMass, MetricMolarMass>) =
    (unit.volume per molarMass.unit.weight).specificVolume(this, molarMass)

@JvmName("imperialMolarVolumeDivImperialMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, ImperialMolarVolume>.div(molarMass: ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>) =
    (unit.volume per molarMass.unit.weight).specificVolume(this, molarMass)

@JvmName("imperialMolarVolumeDivUKImperialMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, ImperialMolarVolume>.div(molarMass: ScientificValue<PhysicalQuantity.MolarMass, UKImperialMolarMass>) =
    (unit.volume per molarMass.unit.weight).specificVolume(this, molarMass)

@JvmName("imperialMolarVolumeDivUSCustomaryMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, ImperialMolarVolume>.div(molarMass: ScientificValue<PhysicalQuantity.MolarMass, USCustomaryMolarMass>) =
    (unit.volume per molarMass.unit.weight).specificVolume(this, molarMass)

@JvmName("ukImperialMolarVolumeDivImperialMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, UKImperialMolarVolume>.div(molarMass: ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>) =
    (unit.volume per molarMass.unit.weight).specificVolume(this, molarMass)

@JvmName("ukImperialMolarVolumeDivUKImperialMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, UKImperialMolarVolume>.div(molarMass: ScientificValue<PhysicalQuantity.MolarMass, UKImperialMolarMass>) =
    (unit.volume per molarMass.unit.weight).specificVolume(this, molarMass)

@JvmName("usCustomaryMolarVolumeDivImperialMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, USCustomaryMolarVolume>.div(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>
) = (unit.volume per molarMass.unit.weight).specificVolume(this, molarMass)

@JvmName("usCustomaryMolarVolumeDivUSCustomaryMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.MolarVolume, USCustomaryMolarVolume>.div(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, USCustomaryMolarMass>
) = (unit.volume per molarMass.unit.weight).specificVolume(this, molarMass)

@JvmName("molarVolumeDivMolarMass")
infix operator fun <MolarVolumeUnit : MolarVolume, MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>.div(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = (CubicMeter per Kilogram).specificVolume(this, molarMass)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarVolume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.volume.volume
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialMolarVolume
import com.splendo.kaluga.scientific.unit.MetricMolarVolume
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.UKImperialMolarVolume
import com.splendo.kaluga.scientific.unit.USCustomaryMolarVolume
import kotlin.jvm.JvmName

@JvmName("metricMolarVolumeTimesAmountOfSubstance")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.MolarVolume, MetricMolarVolume>.times(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = unit.volume.volume(this, amountOfSubstance)

@JvmName("imperialMolarVolumeTimesAmountOfSubstance")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.MolarVolume, ImperialMolarVolume>.times(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = unit.volume.volume(this, amountOfSubstance)

@JvmName("ukImperialMolarVolumeTimesAmountOfSubstance")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.MolarVolume, UKImperialMolarVolume>.times(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = unit.volume.volume(this, amountOfSubstance)

@JvmName("usCustomaryMolarVolumeTimesAmountOfSubstance")
infix operator fun <AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.MolarVolume, USCustomaryMolarVolume>.times(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = unit.volume.volume(this, amountOfSubstance)

@JvmName("molarVolumeTimesAmountOfSubstance")
infix operator fun <MolarVolumeUnit : MolarVolume, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>.times(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = CubicMeter.volume(this, amountOfSubstance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("molarityFromAmountOfSubstanceAndVolumeDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    VolumeUnit : Volume,
    MolarityUnit : Molarity
    > MolarityUnit.molarity(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = molarity(amountOfSubstance, volume, ::DefaultScientificValue)

@JvmName("molarityFromAmountOfSubstanceAndVolume")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    VolumeUnit : Volume,
    MolarityUnit : Molarity,
    Value : ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
    > MolarityUnit.molarity(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    factory: (Decimal, MolarityUnit) -> Value
) = byDividing(amountOfSubstance, volume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.Molarity
import kotlin.jvm.JvmName

@JvmName("molarityFromDensityAndMolalityDefault")
fun <
    MolarityUnit : Molarity,
    DensityUnit : Density,
    MolalityUnit : Molality
    > MolarityUnit.molarity(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>,
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = molarity(molality, density, ::DefaultScientificValue)

@JvmName("molarityFromDensityAndMolality")
fun <
    MolarityUnit : Molarity,
    DensityUnit : Density,
    MolalityUnit : Molality,
    Value : ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
    > MolarityUnit.molarity(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>,
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    factory: (Decimal, MolarityUnit) -> Value
) = byMultiplying(molality, density, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.Molarity
import kotlin.jvm.JvmName

@JvmName("molarityFromDensityAndMolarMassDefault")
fun <
    DensityUnit : Density,
    MolarMassUnit : MolarMass,
    MolarityUnit : Molarity
    > MolarityUnit.molarity(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = molarity(density, molarMass, ::DefaultScientificValue)

@JvmName("molarityFromDensityAndMolarMass")
fun <
    DensityUnit : Density,
    MolarMassUnit : MolarMass,
    MolarityUnit : Molarity,
    Value : ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
    > MolarityUnit.molarity(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    factory: (Decimal, MolarityUnit) -> Value
) = byDividing(density, molarMass, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("molarityFromMolalityAndSpecificVolumeDefault")
fun <
    MolarityUnit : Molarity,
    SpecificVolumeUnit : SpecificVolume,
    MolalityUnit : Molality
    > MolarityUnit.molarity(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>,
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = molarity(molality, specificVolume, ::DefaultScientificValue)

@JvmName("molarityFromMolalityAndSpecificVolume")
fun <
    MolarityUnit : Molarity,
    SpecificVolumeUnit : SpecificVolume,
    MolalityUnit : Molality,
    Value : ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
    > MolarityUnit.molarity(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>,
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    factory: (Decimal, MolarityUnit) -> Value
) = byDividing(molality, specificVolume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byInverting
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.Molarity
import kotlin.jvm.JvmName

@JvmName("molarityFromInvertedMolarVolumeDefault")
fun <
    MolarityUnit : Molarity,
    MolarVolumeUnit : MolarVolume
    > MolarityUnit.molarity(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
) = molarity(molarVolume, ::DefaultScientificValue)

@JvmName("molarityFromInvertedMolarVolume")
fun <
    MolarityUnit : Molarity,
    MolarVolumeUnit : MolarVolume,
    Value : ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
    > MolarityUnit.molarity(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>,
    factory: (Decimal, MolarityUnit) -> Value
) = byInverting(molarVolume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.amountOfSubstance.amountOfSubstance
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("molarityTimesVolume")
infix operator fun <MolarityUnit : Molarity, VolumeUnit : Volume> ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = unit.amountOfSubstance.amountOfSubstance(this, volume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.density.density
import com.splendo.kaluga.scientific.converter.molarMass.times
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialMolality
import com.splendo.kaluga.scientific.unit.ImperialMolarMass
import com.splendo.kaluga.scientific.unit.ImperialMolarity
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricMolality
import com.splendo.kaluga.scientific.unit.MetricMolarMass
import com.splendo.kaluga.scientific.unit.MetricMolarity
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.UKImperialMolality
import com.splendo.kaluga.scientific.unit.UKImperialMolarMass
import com.splendo.kaluga.scientific.unit.UKImperialMolarity
import com.splendo.kaluga.scientific.unit.USCustomaryMolality
import com.splendo.kaluga.scientific.unit.USCustomaryMolarMass
import com.splendo.kaluga.scientific.unit.USCustomaryMolarity
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricMolarityDivMetricMolality")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, MetricMolarity>.div(molality: ScientificValue<PhysicalQuantity.Molality, MetricMolality>) =
    (molality.unit.per per unit.per).density(this, molality)

@JvmName("imperialMolarityDivImperialMolality")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, ImperialMolarity>.div(molality: ScientificValue<PhysicalQuantity.Molality, ImperialMolality>) =
    (molality.unit.per per unit.per).density(this, molality)

@JvmName("imperialMolarityDivUKImperialMolality")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, ImperialMolarity>.div(molality: ScientificValue<PhysicalQuantity.Molality, UKImperialMolality>) =
    (molality.unit.per per unit.per).density(this, molality)

@JvmName("imperialMolarityDivUSCustomaryMolality")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, ImperialMolarity>.div(molality: ScientificValue<PhysicalQuantity.Molality, USCustomaryMolality>) =
    (molality.unit.per per unit.per).density(this, molality)

@JvmName("ukImperialMolarityDivImperialMolality")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, UKImperialMolarity>.div(molality: ScientificValue<PhysicalQuantity.Molality, ImperialMolality>) =
    (molality.unit.per per unit.per).density(this, molality)

@JvmName("ukImperialMolarityDivUKImperialMolality")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, UKImperialMolarity>.div(molality: ScientificValue<PhysicalQuantity.Molality, UKImperialMolality>) =
    (molality.unit.per per unit.per).density(this, molality)

@JvmName("usCustomaryMolarityDivImperialMolality")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, USCustomaryMolarity>.div(molality: ScientificValue<PhysicalQuantity.Molality, ImperialMolality>) =
    (molality.unit.per per unit.per).density(this, molality)

@JvmName("usCustomaryMolarityDivUSCustomaryMolality")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, USCustomaryMolarity>.div(molality: ScientificValue<PhysicalQuantity.Molality, USCustomaryMolality>) =
    (molality.unit.per per unit.per).density(this, molality)

@JvmName("molarityDivMolality")
infix operator fun <MolarityUnit : Molarity, MolalityUnit : Molality> ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = (Kilogram per CubicMeter).density(this, molality)

@JvmName("metricMolarityTimesMetricMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, MetricMolarity>.times(molarMass: ScientificValue<PhysicalQuantity.MolarMass, MetricMolarMass>) =
    molarMass * this

@JvmName("imperialMolarityTimesImperialMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, ImperialMolarity>.times(molarMass: ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>) =
    molarMass * this

@JvmName("imperialMolarityTimesUKImperialMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, ImperialMolarity>.times(molarMass: ScientificValue<PhysicalQuantity.MolarMass, UKImperialMolarMass>) =
    molarMass * this

@JvmName("imperialMolarityTimesUSCustomaryMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, ImperialMolarity>.times(molarMass: ScientificValue<PhysicalQuantity.MolarMass, USCustomaryMolarMass>) =
    molarMass * this

@JvmName("ukImperialMolarityTimesImperialMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, UKImperialMolarity>.times(molarMass: ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>) =
    molarMass * this

@JvmName("ukImperialMolarityTimesUKImperialMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, UKImperialMolarity>.times(molarMass: ScientificValue<PhysicalQuantity.MolarMass, UKImperialMolarMass>) =
    molarMass * this

@JvmName("usCustomaryMolarityTimesImperialMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, USCustomaryMolarity>.times(molarMass: ScientificValue<PhysicalQuantity.MolarMass, ImperialMolarMass>) =
    molarMass * this

@JvmName("usCustomaryMolarityTimesUSCustomaryMolarMass")
infix operator fun ScientificValue<PhysicalQuantity.Molarity, USCustomaryMolarity>.times(molarMass: ScientificValue<PhysicalQuantity.MolarMass, USCustomaryMolarMass>) =
    molarMass * this

@JvmName("molarityTimesMolarMass")
infix operator fun <MolarityUnit : Molarity, MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = molarMass * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molality.molality
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("molarityDivMetricDensity")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, MetricDensity>
) = (unit.amountOfSubstance per density.unit.weight).molality(this, density)

@JvmName("molarityDivImperialDensity")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>
) = (unit.amountOfSubstance per density.unit.weight).molality(this, density)

@JvmName("molarityDivUKImperialDensity")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, UKImperialDensity>
) = (unit.amountOfSubstance per density.unit.weight).molality(this, density)

@JvmName("molarityDivUSCustomaryDensity")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>
) = (unit.amountOfSubstance per density.unit.weight).molality(this, density)

@JvmName("molarityDivDensity")
infix operator fun <MolarityUnit : Molarity, DensityUnit : Density> ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = (unit.amountOfSubstance per Kilogram).molality(this, density)

@JvmName("molarityTimesMetricSpecificVolume")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>
) = (unit.amountOfSubstance per specificVolume.unit.per).molality(this, specificVolume)

@JvmName("molarityTimesImperialSpecificVolume")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = (unit.amountOfSubstance per specificVolume.unit.per).molality(this, specificVolume)

@JvmName("molarityTimesUKImperialSpecificVolume")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>
) = (unit.amountOfSubstance per specificVolume.unit.per).molality(this, specificVolume)

@JvmName("molarityTimesUSCustomarySpecificVolume")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>
) = (unit.amountOfSubstance per specificVolume.unit.per).molality(this, specificVolume)

@JvmName("molarityTimesSpecificVolume")
infix operator fun <MolarityUnit : Molarity, SpecificVolumeUnit : SpecificVolume> ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = (unit.amountOfSubstance per Kilogram).molality(this, specificVolume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.molarity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarVolume.molarVolume
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialMolarity
import com.splendo.kaluga.scientific.unit.MetricMolarity
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.UKImperialMolarity
import com.splendo.kaluga.scientific.unit.USCustomaryMolarity
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricMolarityMolarVolume")
fun ScientificValue<PhysicalQuantity.Molarity, MetricMolarity>.molarVolume() =
    (unit.per per unit.amountOfSubstance).molarVolume(this)

@JvmName("imperialMolarityMolarVolume")
fun ScientificValue<PhysicalQuantity.Molarity, ImperialMolarity>.molarVolume() =
    (unit.per per unit.amountOfSubstance).molarVolume(this)

@JvmName("ukImperialMolarityMolarVolume")
fun ScientificValue<PhysicalQuantity.Molarity, UKImperialMolarity>.molarVolume() =
    (unit.per per unit.amountOfSubstance).molarVolume(this)

@JvmName("usCustomaryMolarityMolarVolume")
fun ScientificValue<PhysicalQuantity.Molarity, USCustomaryMolarity>.molarVolume() =
    (unit.per per unit.amountOfSubstance).molarVolume(this)

@JvmName("molarityMolarVolume")
fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>.molarVolume() =
    (CubicMeter per unit.amountOfSubstance).molarVolume(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.momentum

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.DynamicViscosity
import com.splendo.kaluga.scientific.unit.Momentum
import kotlin.jvm.JvmName

@JvmName("momentumFromDynamicViscosityAndAreaDefault")
fun <
    MomentumUnit : Momentum,
    AreaUnit : Area,
    DynamicViscosityUnit : DynamicViscosity
    > MomentumUnit.momentum(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, DynamicViscosityUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = momentum(dynamicViscosity, area, ::DefaultScientificValue)

@JvmName("momentumFromDynamicViscosityAndArea")
fun <
    MomentumUnit : Momentum,
    AreaUnit : Area,
    DynamicViscosityUnit : DynamicViscosity,
    Value : ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>
    > MomentumUnit.momentum(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, DynamicViscosityUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, MomentumUnit) -> Value
) = byMultiplying(dynamicViscosity, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.momentum

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Momentum
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("momentumFromForceAndTimeDefault")
fun <
    ForceUnit : Force,
    TimeUnit : Time,
    MomentumUnit : Momentum
    > MomentumUnit.momentum(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = momentum(force, time, ::DefaultScientificValue)

@JvmName("momentumFromForceAndTime")
fun <
    ForceUnit : Force,
    TimeUnit : Time,
    MomentumUnit : Momentum,
    Value : ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>
    > MomentumUnit.momentum(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, MomentumUnit) -> Value
) = byMultiplying(force, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.momentum

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Momentum
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("momentumFromMassAndSpeedDefault")
fun <
    WeightUnit : Weight,
    SpeedUnit : Speed,
    MomentumUnit : Momentum
    > MomentumUnit.momentum(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>
) = momentum(mass, speed, ::DefaultScientificValue)

@JvmName("momentumFromMassAndSpeed")
fun <
    WeightUnit : Weight,
    SpeedUnit : Speed,
    MomentumUnit : Momentum,
    Value : ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>
    > MomentumUnit.momentum(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>,
    factory: (Decimal, MomentumUnit) -> Value
) = byMultiplying(mass, speed, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.momentum

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.area
import com.splendo.kaluga.scientific.converter.force.div
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.DynamicViscosity
import com.splendo.kaluga.scientific.unit.ImperialDynamicViscosity
import com.splendo.kaluga.scientific.unit.ImperialMomentum
import com.splendo.kaluga.scientific.unit.MetricDynamicViscosity
import com.splendo.kaluga.scientific.unit.MetricMomentum
import com.splendo.kaluga.scientific.unit.Momentum
import com.splendo.kaluga.scientific.unit.UKImperialDynamicViscosity
import com.splendo.kaluga.scientific.unit.UKImperialMomentum
import com.splendo.kaluga.scientific.unit.USCustomaryDynamicViscosity
import com.splendo.kaluga.scientific.unit.USCustomaryMomentum
import kotlin.jvm.JvmName

@JvmName("metricMomentumDivMetricDynamicViscosity")
infix operator fun ScientificValue<PhysicalQuantity.Momentum, MetricMomentum>.div(dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, MetricDynamicViscosity>) =
    ((this / 1(unit.speed.per)) / 1(dynamicViscosity.unit.pressure)).unit.area(
        this,
        dynamicViscosity
    )

@JvmName("imperialMomentumDivImperialDynamicViscosity")
infix operator fun ScientificValue<PhysicalQuantity.Momentum, ImperialMomentum>.div(dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, ImperialDynamicViscosity>) =
    ((this / 1(unit.speed.per)) / 1(dynamicViscosity.unit.pressure)).unit.area(
        this,
        dynamicViscosity
    )

@JvmName("imperialMomentumDivUKImperialDynamicViscosity")
infix operator fun ScientificValue<PhysicalQuantity.Momentum, ImperialMomentum>.div(dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, UKImperialDynamicViscosity>) =
    ((this / 1(unit.speed.per)) / 1(dynamicViscosity.unit.pressure)).unit.area(
        this,
        dynamicViscosity
    )

@JvmName("imperialMomentumDivUSCustomaryDynamicViscosity")
infix operator fun ScientificValue<PhysicalQuantity.Momentum, ImperialMomentum>.div(dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, USCustomaryDynamicViscosity>) =
    ((this / 1(unit.speed.per)) / 1(dynamicViscosity.unit.pressure)).unit.area(
        this,
        dynamicViscosity
    )

@JvmName("ukImperialMomentumDivImperialDynamicViscosity")
infix operator fun ScientificValue<PhysicalQuantity.Momentum, UKImperialMomentum>.div(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, ImperialDynamicViscosity>
) = ((this / 1(unit.speed.per)) / 1(dynamicViscosity.unit.pressure)).unit.area(
    this,
    dynamicViscosity
)

@JvmName("ukImperialMomentumDivUKImperialDynamicViscosity")
infix operator fun ScientificValue<PhysicalQuantity.Momentum, UKImperialMomentum>.div(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, UKImperialDynamicViscosity>
) = ((this / 1(unit.speed.per)) / 1(dynamicViscosity.unit.pressure)).unit.area(
    this,
    dynamicViscosity
)

@JvmName("usCustomaryMomentumDivImperialDynamicViscosity")
infix operator fun ScientificValue<PhysicalQuantity.Momentum, USCustomaryMomentum>.div(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, ImperialDynamicViscosity>
) = ((this / 1(unit.speed.per)) / 1(dynamicViscosity.unit.pressure)).unit.area(
    this,
    dynamicViscosity
)

@JvmName("usCustomaryMomentumDivUSCustomaryDynamicViscosity")
infix operator fun ScientificValue<PhysicalQuantity.Momentum, USCustomaryMomentum>.div(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, USCustomaryDynamicViscosity>
) = ((this / 1(unit.speed.per)) / 1(dynamicViscosity.unit.pressure)).unit.area(
    this,
    dynamicViscosity
)

@JvmName("momentumDivDynamicViscosity")
infix operator fun <DynamicViscosityUnit : DynamicViscosity, MomentumUnit : Momentum> ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>.div(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, DynamicViscosityUnit>
) = ((this / 1(unit.speed.per)) / 1(dynamicViscosity.unit.pressure)).unit.area(
    this,
    dynamicViscosity
)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.momentum

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.dynamicViscosity.dynamicViscosity
import com.splendo.kaluga.scientific.converter.force.div
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialMomentum
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricMomentum
import com.splendo.kaluga.scientific.unit.Momentum
import com.splendo.kaluga.scientific.unit.UKImperialMomentum
import com.splendo.kaluga.scientific.unit.USCustomaryMomentum
import com.splendo.kaluga.scientific.unit.x
import kotlin.jvm.JvmName

@JvmName("metricMomentumDivMetricArea")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.Momentum, MetricMomentum>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (((this / 1(unit.speed.per)) / area).unit x unit.speed.per).dynamicViscosity(this, area)

@JvmName("imperialMomentumDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Momentum, ImperialMomentum>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (((this / 1(unit.speed.per)) / area).unit x unit.speed.per).dynamicViscosity(this, area)

@JvmName("ukImperialMomentumDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Momentum, UKImperialMomentum>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (((this / 1(unit.speed.per)) / area).unit x unit.speed.per).dynamicViscosity(this, area)

@JvmName("usCustomaryMomentumDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Momentum, USCustomaryMomentum>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (((this / 1(unit.speed.per)) / area).unit x unit.speed.per).dynamicViscosity(this, area)

@JvmName("momentumDivArea")
infix operator fun <MomentumUnit : Momentum, AreaUnit : Area> ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (((this / 1(unit.speed.per)) / area).unit x unit.speed.per).dynamicViscosity(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.momentum

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.force.force
import com.splendo.kaluga.scientific.unit.ImperialMomentum
import com.splendo.kaluga.scientific.unit.MetricMomentum
import com.splendo.kaluga.scientific.unit.Momentum
import com.splendo.kaluga.scientific.unit.Newton
import com.splendo.kaluga.scientific.unit.PoundForce
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.UKImperialMomentum
import com.splendo.kaluga.scientific.unit.USCustomaryMomentum
import com.splendo.kaluga.scientific.unit.ukImperial
import com.splendo.kaluga.scientific.unit.usCustomary
import kotlin.jvm.JvmName

@JvmName("metricMomentumDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Momentum, MetricMomentum>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Newton.force(this, time)

@JvmName("imperialMomentumDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Momentum, ImperialMomentum>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = PoundForce.force(this, time)

@JvmName("ukImperialMomentumDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Momentum, UKImperialMomentum>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = PoundForce.ukImperial.force(this, time)

@JvmName("usCustomaryMomentumDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Momentum, USCustomaryMomentum>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = PoundForce.usCustomary.force(this, time)

@JvmName("momentumDivTime")
infix operator fun <MomentumUnit : Momentum, TimeUnit : Time> ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Newton.force(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.momentum

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.speed.speed
import com.splendo.kaluga.scientific.unit.ImperialMomentum
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricMomentum
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.Momentum
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.UKImperialMomentum
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryMomentum
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricMomentumDivMetricMass")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Momentum, MetricMomentum>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.speed.speed(this, mass)

@JvmName("imperialMomentumDivImperialMass")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Momentum, ImperialMomentum>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.speed.speed(this, mass)

@JvmName("imperialMomentumDivUKImperialMass")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Momentum, ImperialMomentum>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.speed.speed(this, mass)

@JvmName("imperialMomentumDivUSCustomaryMass")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Momentum, ImperialMomentum>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.speed.speed(this, mass)

@JvmName("ukImperialMomentumDivImperialMass")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Momentum, UKImperialMomentum>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.speed.speed(this, mass)

@JvmName("ukImperialMomentumDivUKImperialMass")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Momentum, UKImperialMomentum>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.speed.speed(this, mass)

@JvmName("usCustomaryMomentumDivImperialMass")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Momentum, USCustomaryMomentum>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.speed.speed(this, mass)

@JvmName("usCustomaryMomentumDivUSCustomaryMass")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Momentum, USCustomaryMomentum>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.speed.speed(this, mass)

@JvmName("momentumDivMass")
infix operator fun <MomentumUnit : Momentum, WeightUnit : Weight> ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>.div(
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (Meter per Second).speed(this, mass)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.momentum

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.weight.mass
import com.splendo.kaluga.scientific.unit.ImperialMomentum
import com.splendo.kaluga.scientific.unit.ImperialSpeed
import com.splendo.kaluga.scientific.unit.MetricMomentum
import com.splendo.kaluga.scientific.unit.MetricSpeed
import com.splendo.kaluga.scientific.unit.Momentum
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.UKImperialMomentum
import com.splendo.kaluga.scientific.unit.USCustomaryMomentum
import kotlin.jvm.JvmName

@JvmName("metricMomentumDivMetricSpeed")
infix operator fun ScientificValue<PhysicalQuantity.Momentum, MetricMomentum>.div(speed: ScientificValue<PhysicalQuantity.Speed, MetricSpeed>) =
    unit.mass.mass(this, speed)

@JvmName("imperialMomentumDivImperialSpeed")
infix operator fun ScientificValue<PhysicalQuantity.Momentum, ImperialMomentum>.div(speed: ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>) =
    unit.mass.mass(this, speed)

@JvmName("ukImperialMomentumDivImperialSpeed")
infix operator fun ScientificValue<PhysicalQuantity.Momentum, UKImperialMomentum>.div(speed: ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>) =
    unit.mass.mass(this, speed)

@JvmName("usCustomaryMomentumDivImperialSpeed")
infix operator fun ScientificValue<PhysicalQuantity.Momentum, USCustomaryMomentum>.div(speed: ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>) =
    unit.mass.mass(this, speed)

@JvmName("momentumDivSpeed")
infix operator fun <MomentumUnit : Momentum, SpeedUnit : Speed> ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>.div(
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>
) = unit.mass.mass(this, speed)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.power

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("powerFromEnergyAndTimeDefault")
fun <
    EnergyUnit : Energy,
    TimeUnit : Time,
    PowerUnit : Power
    > PowerUnit.power(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = power(energy, time, ::DefaultScientificValue)

@JvmName("powerFromEnergyAndTime")
fun <
    EnergyUnit : Energy,
    TimeUnit : Time,
    PowerUnit : Power,
    Value : ScientificValue<PhysicalQuantity.Power, PowerUnit>
    > PowerUnit.power(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, PowerUnit) -> Value
) = byDividing(energy, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.power

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Speed
import kotlin.jvm.JvmName

@JvmName("powerFromForceAndSpeedDefault")
fun <
    ForceUnit : Force,
    SpeedUnit : Speed,
    PowerUnit : Power
    > PowerUnit.power(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>
) = power(force, speed, ::DefaultScientificValue)

@JvmName("powerFromForceAndSpeed")
fun <
    ForceUnit : Force,
    SpeedUnit : Speed,
    PowerUnit : Power,
    Value : ScientificValue<PhysicalQuantity.Power, PowerUnit>
    > PowerUnit.power(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>,
    factory: (Decimal, PowerUnit) -> Value
) = byMultiplying(force, speed, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.power

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.converter.temperature.deltaValue
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Temperature
import com.splendo.kaluga.scientific.unit.ThermalResistance
import kotlin.jvm.JvmName

@JvmName("powerFromTemperatureAndThermalResistanceDefault")
fun <
    TemperatureUnit : Temperature,
    PowerUnit : Power,
    ThermalResistanceUnit : ThermalResistance
    > PowerUnit.power(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>,
    thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, ThermalResistanceUnit>
) = power(temperature, thermalResistance, ::DefaultScientificValue)

@JvmName("powerFromTemperatureAndThermalResistance")
fun <
    TemperatureUnit : Temperature,
    PowerUnit : Power,
    ThermalResistanceUnit : ThermalResistance,
    Value : ScientificValue<PhysicalQuantity.Power, PowerUnit>
    > PowerUnit.power(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>,
    thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, ThermalResistanceUnit>,
    factory: (Decimal, PowerUnit) -> Value
) = byDividing(Kelvin.deltaValue(temperature), thermalResistance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.power

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("powerFromVoltageAndCurrentDefault")
fun <
    VoltageUnit : Voltage,
    ElectricCurrentUnit : ElectricCurrent,
    PowerUnit : Power
    > PowerUnit.power(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, ElectricCurrentUnit>
) = power(voltage, current, ::DefaultScientificValue)

@JvmName("powerFromVoltageAndCurrent")
fun <
    VoltageUnit : Voltage,
    ElectricCurrentUnit : ElectricCurrent,
    PowerUnit : Power,
    Value : ScientificValue<PhysicalQuantity.Power, PowerUnit>
    > PowerUnit.power(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, ElectricCurrentUnit>,
    factory: (Decimal, PowerUnit) -> Value
) = byMultiplying(voltage, current, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.power

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCurrent.current
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.Ampere
import com.splendo.kaluga.scientific.unit.ErgPerSecond
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("ergSecondDivAbvolt")
infix operator fun ScientificValue<PhysicalQuantity.Power, ErgPerSecond>.div(voltage: ScientificValue<PhysicalQuantity.Voltage, Abvolt>) =
    Abampere.current(this, voltage)

@JvmName("powerDivVoltage")
infix operator fun <PowerUnit : Power, VoltageUnit : Voltage> ScientificValue<PhysicalQuantity.Power, PowerUnit>.div(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = Ampere.current(this, voltage)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.power

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.energy.energy
import com.splendo.kaluga.scientific.unit.BritishThermalUnit
import com.splendo.kaluga.scientific.unit.BritishThermalUnitPerHour
import com.splendo.kaluga.scientific.unit.BritishThermalUnitPerMinute
import com.splendo.kaluga.scientific.unit.BritishThermalUnitPerSecond
import com.splendo.kaluga.scientific.unit.Centiwatt
import com.splendo.kaluga.scientific.unit.CentiwattHour
import com.splendo.kaluga.scientific.unit.Decawatt
import com.splendo.kaluga.scientific.unit.DecawattHour
import com.splendo.kaluga.scientific.unit.Deciwatt
import com.splendo.kaluga.scientific.unit.DeciwattHour
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.ErgPerSecond
import com.splendo.kaluga.scientific.unit.FootPoundForce
import com.splendo.kaluga.scientific.unit.Gigawatt
import com.splendo.kaluga.scientific.unit.GigawattHour
import com.splendo.kaluga.scientific.unit.Hectowatt
import com.splendo.kaluga.scientific.unit.HectowattHour
import com.splendo.kaluga.scientific.unit.Horsepower
import com.splendo.kaluga.scientific.unit.HorsepowerHour
import com.splendo.kaluga.scientific.unit.Hour
import com.splendo.kaluga.scientific.unit.ImperialPower
import com.splendo.kaluga.scientific.unit.InchPoundForce
import com.splendo.kaluga.scientific.unit.InchPoundForcePerMinute
import com.splendo.kaluga.scientific.unit.InchPoundForcePerSecond
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Kilowatt
import com.splendo.kaluga.scientific.unit.KilowattHour
import com.splendo.kaluga.scientific.unit.Megawatt
import com.splendo.kaluga.scientific.unit.MegawattHour
import com.splendo.kaluga.scientific.unit.MetricPower
import com.splendo.kaluga.scientific.unit.Microwatt
import com.splendo.kaluga.scientific.unit.MicrowattHour
import com.splendo.kaluga.scientific.unit.Milliwatt
import com.splendo.kaluga.scientific.unit.MilliwattHour
import com.splendo.kaluga.scientific.unit.Nanowatt
import com.splendo.kaluga.scientific.unit.NanowattHour
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Watt
import com.splendo.kaluga.scientific.unit.WattHour
import kotlin.jvm.JvmName

@JvmName("wattTimesHour")
infix operator fun ScientificValue<PhysicalQuantity.Power, Watt>.times(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    WattHour.energy(this, time)

@JvmName("nanowattTimesHour")
infix operator fun ScientificValue<PhysicalQuantity.Power, Nanowatt>.times(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    NanowattHour.energy(this, time)

@JvmName("microwattTimesHour")
infix operator fun ScientificValue<PhysicalQuantity.Power, Microwatt>.times(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    MicrowattHour.energy(this, time)

@JvmName("milliwattTimesHour")
infix operator fun ScientificValue<PhysicalQuantity.Power, Milliwatt>.times(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    MilliwattHour.energy(this, time)

@JvmName("centiwattTimesHour")
infix operator fun ScientificValue<PhysicalQuantity.Power, Centiwatt>.times(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    CentiwattHour.energy(this, time)

@JvmName("deciwattTimesHour")
infix operator fun ScientificValue<PhysicalQuantity.Power, Deciwatt>.times(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    DeciwattHour.energy(this, time)

@JvmName("decawattTimesHour")
infix operator fun ScientificValue<PhysicalQuantity.Power, Decawatt>.times(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    DecawattHour.energy(this, time)

@JvmName("hectowattTimesHour")
infix operator fun ScientificValue<PhysicalQuantity.Power, Hectowatt>.times(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    HectowattHour.energy(this, time)

@JvmName("kilowattTimesHour")
infix operator fun ScientificValue<PhysicalQuantity.Power, Kilowatt>.times(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    KilowattHour.energy(this, time)

@JvmName("megawattTimesHour")
infix operator fun ScientificValue<PhysicalQuantity.Power, Megawatt>.times(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    MegawattHour.energy(this, time)

@JvmName("gigawattTimesHour")
infix operator fun ScientificValue<PhysicalQuantity.Power, Gigawatt>.times(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    GigawattHour.energy(this, time)

@JvmName("ergPerSecondTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Power, ErgPerSecond>.times(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    Erg.energy(this, time)

@JvmName("metricPowerTimesTime")
infix operator fun <PowerUnit : MetricPower, TimeUnit : Time> ScientificValue<PhysicalQuantity.Power, PowerUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Joule.energy(this, time)

@JvmName("inchPoundForcePerSecondTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Power, InchPoundForcePerSecond>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = InchPoundForce.energy(this, time)

@JvmName("inchPoundForcePerMinuteTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Power, InchPoundForcePerMinute>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = InchPoundForce.energy(this, time)

@JvmName("horsepowerTimesHour")
infix operator fun ScientificValue<PhysicalQuantity.Power, Horsepower>.times(time: ScientificValue<PhysicalQuantity.Time, Hour>) =
    HorsepowerHour.energy(this, time)

@JvmName("britishThermalUnitPerSecondTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Power, BritishThermalUnitPerSecond>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = BritishThermalUnit.energy(this, time)

@JvmName("britishThermalUnitPerMinuteTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Power, BritishThermalUnitPerMinute>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = BritishThermalUnit.energy(this, time)

@JvmName("britishThermalUnitPerHourTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Power, BritishThermalUnitPerHour>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = BritishThermalUnit.energy(this, time)

@JvmName("imperialPowerTimesTime")
infix operator fun <PowerUnit : ImperialPower, TimeUnit : Time> ScientificValue<PhysicalQuantity.Power, PowerUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = FootPoundForce.energy(this, time)

@JvmName("powerTimesTime")
infix operator fun <PowerUnit : Power, TimeUnit : Time> ScientificValue<PhysicalQuantity.Power, PowerUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Joule.energy(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.power

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.force.force
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.ErgPerSecond
import com.splendo.kaluga.scientific.unit.ImperialPower
import com.splendo.kaluga.scientific.unit.ImperialSpeed
import com.splendo.kaluga.scientific.unit.MetricAndImperialPower
import com.splendo.kaluga.scientific.unit.MetricPower
import com.splendo.kaluga.scientific.unit.MetricSpeed
import com.splendo.kaluga.scientific.unit.Newton
import com.splendo.kaluga.scientific.unit.PoundForce
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Speed
import kotlin.jvm.JvmName

@JvmName("ergPerSecondDivMetricSpeed")
infix operator fun ScientificValue<PhysicalQuantity.Power, ErgPerSecond>.div(speed: ScientificValue<PhysicalQuantity.Speed, MetricSpeed>) =
    Dyne.force(this, speed)

@JvmName("metricPowerDivMetricSpeed")
infix operator fun <PowerUnit : MetricPower> ScientificValue<PhysicalQuantity.Power, PowerUnit>.div(
    speed: ScientificValue<PhysicalQuantity.Speed, MetricSpeed>
) = Newton.force(this, speed)

@JvmName("imperialPowerDivImperialSpeed")
infix operator fun <PowerUnit : ImperialPower> ScientificValue<PhysicalQuantity.Power, PowerUnit>.div(
    speed: ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>
) = PoundForce.force(this, speed)

@JvmName("metricAndImperialPowerDivImperialSpeed")
infix operator fun <PowerUnit : MetricAndImperialPower> ScientificValue<PhysicalQuantity.Power, PowerUnit>.div(
    speed: ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>
) = PoundForce.force(this, speed)

@JvmName("powerDivSpeed")
infix operator fun <PowerUnit : Power, SpeedUnit : Speed> ScientificValue<PhysicalQuantity.Power, PowerUnit>.div(
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>
) = Newton.force(this, speed)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.power

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.speed.speed
import com.splendo.kaluga.scientific.unit.BritishThermalUnitPerHour
import com.splendo.kaluga.scientific.unit.BritishThermalUnitPerMinute
import com.splendo.kaluga.scientific.unit.BritishThermalUnitPerSecond
import com.splendo.kaluga.scientific.unit.Centimeter
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.ErgPerSecond
import com.splendo.kaluga.scientific.unit.Foot
import com.splendo.kaluga.scientific.unit.FootPoundForcePerMinute
import com.splendo.kaluga.scientific.unit.FootPoundForcePerSecond
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Hour
import com.splendo.kaluga.scientific.unit.ImperialForce
import com.splendo.kaluga.scientific.unit.ImperialPower
import com.splendo.kaluga.scientific.unit.Inch
import com.splendo.kaluga.scientific.unit.InchPoundForcePerMinute
import com.splendo.kaluga.scientific.unit.InchPoundForcePerSecond
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricAndImperialPower
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.MetricPower
import com.splendo.kaluga.scientific.unit.Minute
import com.splendo.kaluga.scientific.unit.PoundForce
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.UKImperialForce
import com.splendo.kaluga.scientific.unit.USCustomaryForce
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("ergPerSecondDivDyne")
infix operator fun ScientificValue<PhysicalQuantity.Power, ErgPerSecond>.div(force: ScientificValue<PhysicalQuantity.Force, Dyne>) =
    (Centimeter per Second).speed(this, force)

@JvmName("ergPerSecondDivDyneMultiple")
infix operator fun <DyneUnit> ScientificValue<PhysicalQuantity.Power, ErgPerSecond>.div(force: ScientificValue<PhysicalQuantity.Force, DyneUnit>) where DyneUnit : Force, DyneUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> =
    (Centimeter per Second).speed(this, force)

@JvmName("metricPowerDivMetricForce")
infix operator fun <PowerUnit : MetricPower, ForceUnit : Force> ScientificValue<PhysicalQuantity.Power, PowerUnit>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = (Meter per Second).speed(this, force)

@JvmName("footPoundForcePerSecondDivImperialForce")
infix operator fun <ForceUnit : ImperialForce> ScientificValue<PhysicalQuantity.Power, FootPoundForcePerSecond>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = (Foot per Second).speed(this, force)

@JvmName("footPoundForcePerMinuteDivImperialForce")
infix operator fun <ForceUnit : ImperialForce> ScientificValue<PhysicalQuantity.Power, FootPoundForcePerMinute>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = (Foot per Minute).speed(this, force)

@JvmName("inchPoundForcePerSecondDivImperialForce")
infix operator fun <ForceUnit : ImperialForce> ScientificValue<PhysicalQuantity.Power, InchPoundForcePerSecond>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = (Inch per Second).speed(this, force)

@JvmName("inchPoundForcePerMinuteDivImperialForce")
infix operator fun <ForceUnit : ImperialForce> ScientificValue<PhysicalQuantity.Power, InchPoundForcePerMinute>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = (Inch per Minute).speed(this, force)

@JvmName("britishThermalUnitPerSecondDivPoundForce")
infix operator fun ScientificValue<PhysicalQuantity.Power, BritishThermalUnitPerSecond>.div(force: ScientificValue<PhysicalQuantity.Force, PoundForce>) =
    (Foot per Second).speed(this, force)

@JvmName("britishThermalUnitPerMinuteDivPoundForce")
infix operator fun ScientificValue<PhysicalQuantity.Power, BritishThermalUnitPerMinute>.div(force: ScientificValue<PhysicalQuantity.Force, PoundForce>) =
    (Foot per Minute).speed(this, force)

@JvmName("britishThermalUnitPerHourDivPoundForce")
infix operator fun ScientificValue<PhysicalQuantity.Power, BritishThermalUnitPerHour>.div(force: ScientificValue<PhysicalQuantity.Force, PoundForce>) =
    (Foot per Hour).speed(this, force)

@JvmName("imperialPowerDivImperialForce")
infix operator fun <PowerUnit : ImperialPower, ForceUnit : ImperialForce> ScientificValue<PhysicalQuantity.Power, PowerUnit>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = (Foot per Second).speed(this, force)

@JvmName("imperialPowerDivUKImperialForce")
infix operator fun <PowerUnit : ImperialPower, ForceUnit : UKImperialForce> ScientificValue<PhysicalQuantity.Power, PowerUnit>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = (Foot per Second).speed(this, force)

@JvmName("imperialPowerDivUSCustomaryForce")
infix operator fun <PowerUnit : ImperialPower, ForceUnit : USCustomaryForce> ScientificValue<PhysicalQuantity.Power, PowerUnit>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = (Foot per Second).speed(this, force)

@JvmName("metricAndImperialPowerDivImperialForce")
infix operator fun <PowerUnit : MetricAndImperialPower, ForceUnit : ImperialForce> ScientificValue<PhysicalQuantity.Power, PowerUnit>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = (Foot per Second).speed(this, force)

@JvmName("metricAndImperialPowerDivUKImperialForce")
infix operator fun <PowerUnit : MetricAndImperialPower, ForceUnit : UKImperialForce> ScientificValue<PhysicalQuantity.Power, PowerUnit>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = (Foot per Second).speed(this, force)

@JvmName("metricAndImperialPowerDivUSCustomaryForce")
infix operator fun <PowerUnit : MetricAndImperialPower, ForceUnit : USCustomaryForce> ScientificValue<PhysicalQuantity.Power, PowerUnit>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = (Foot per Second).speed(this, force)

@JvmName("powerDivForce")
infix operator fun <PowerUnit : Power, ForceUnit : Force> ScientificValue<PhysicalQuantity.Power, PowerUnit>.div(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = (Meter per Second).speed(this, force)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.power

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.thermalResistance.times
import com.splendo.kaluga.scientific.unit.ImperialPower
import com.splendo.kaluga.scientific.unit.MetricAndImperialPower
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialThermalResistance
import com.splendo.kaluga.scientific.unit.MetricPower
import com.splendo.kaluga.scientific.unit.MetricThermalResistance
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.ThermalResistance
import com.splendo.kaluga.scientific.unit.UKImperialThermalResistance
import com.splendo.kaluga.scientific.unit.USCustomaryThermalResistance
import kotlin.jvm.JvmName

@JvmName("powerTimesMetricAndUKImperialThermalResistance")
infix operator fun <PowerUnit : Power> ScientificValue<PhysicalQuantity.Power, PowerUnit>.times(
    thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, MetricAndUKImperialThermalResistance>
) = thermalResistance * this

@JvmName("metricAndImperialPowerTimesMetricThermalResistance")
infix operator fun <PowerUnit : MetricAndImperialPower> ScientificValue<PhysicalQuantity.Power, PowerUnit>.times(
    thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, MetricThermalResistance>
) = thermalResistance * this

@JvmName("metricPowerTimesMetricThermalResistance")
infix operator fun <PowerUnit : MetricPower> ScientificValue<PhysicalQuantity.Power, PowerUnit>.times(thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, MetricThermalResistance>) =
    thermalResistance * this

@JvmName("metricAndImperialPowerTimesUKImperialThermalResistance")
infix operator fun <PowerUnit : MetricAndImperialPower> ScientificValue<PhysicalQuantity.Power, PowerUnit>.times(
    thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, UKImperialThermalResistance>
) = thermalResistance * this

@JvmName("imperialPowerTimesUKImperialThermalResistance")
infix operator fun <PowerUnit : ImperialPower> ScientificValue<PhysicalQuantity.Power, PowerUnit>.times(thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, UKImperialThermalResistance>) =
    thermalResistance * this

@JvmName("metricAndImperialPowerTimesUSCustomaryThermalResistance")
infix operator fun <PowerUnit : MetricAndImperialPower> ScientificValue<PhysicalQuantity.Power, PowerUnit>.times(
    thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, USCustomaryThermalResistance>
) = thermalResistance * this

@JvmName("imperialPowerTimesUSCustomaryThermalResistance")
infix operator fun <PowerUnit : ImperialPower> ScientificValue<PhysicalQuantity.Power, PowerUnit>.times(thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, USCustomaryThermalResistance>) =
    thermalResistance * this

@JvmName("powerTimesThermalResistance")
infix operator fun <ThermalResistanceUnit : ThermalResistance, PowerUnit : Power> ScientificValue<PhysicalQuantity.Power, PowerUnit>.times(
    thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, ThermalResistanceUnit>
) = thermalResistance * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.power

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.voltage.voltage
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.Biot
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.ErgPerSecond
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Volt
import kotlin.jvm.JvmName

@JvmName("ergSecondDivAbampere")
infix operator fun ScientificValue<PhysicalQuantity.Power, ErgPerSecond>.div(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>) =
    Abvolt.voltage(this, current)

@JvmName("ergSecondDivBiot")
infix operator fun ScientificValue<PhysicalQuantity.Power, ErgPerSecond>.div(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>) =
    Abvolt.voltage(this, current)

@JvmName("powerDivCurrent")
infix operator fun <PowerUnit : Power, CurrentUnit : ElectricCurrent> ScientificValue<PhysicalQuantity.Power, PowerUnit>.div(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
) = Volt.voltage(this, current)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.pressure

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.DynamicViscosity
import com.splendo.kaluga.scientific.unit.Pressure
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("pressureFromDynamicViscosityAndTimeDefault")
fun <
    DynamicViscosityUnit : DynamicViscosity,
    TimeUnit : Time,
    PressureUnit : Pressure
    > PressureUnit.pressure(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, DynamicViscosityUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = pressure(dynamicViscosity, time, ::DefaultScientificValue)

@JvmName("pressureFromDynamicViscosityAndTime")
fun <
    DynamicViscosityUnit : DynamicViscosity,
    TimeUnit : Time,
    PressureUnit : Pressure,
    Value : ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
    > PressureUnit.pressure(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, DynamicViscosityUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, PressureUnit) -> Value
) = byDividing(dynamicViscosity, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.pressure

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Pressure
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("pressureFromEnergyAndVolumeDefault")
fun <
    EnergyUnit : Energy,
    VolumeUnit : Volume,
    PressureUnit : Pressure
    > PressureUnit.pressure(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = pressure(energy, volume, ::DefaultScientificValue)

@JvmName("pressureFromEnergyAndVolume")
fun <
    EnergyUnit : Energy,
    VolumeUnit : Volume,
    PressureUnit : Pressure,
    Value : ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
    > PressureUnit.pressure(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    factory: (Decimal, PressureUnit) -> Value
) = byDividing(energy, volume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.pressure

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Pressure
import kotlin.jvm.JvmName

@JvmName("pressureFromForceAndAreaDefault")
fun <
    ForceUnit : Force,
    AreaUnit : Area,
    PressureUnit : Pressure
    > PressureUnit.pressure(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = pressure(force, area, ::DefaultScientificValue)

@JvmName("pressureFromForceAndArea")
fun <
    ForceUnit : Force,
    AreaUnit : Area,
    PressureUnit : Pressure,
    Value : ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
    > PressureUnit.pressure(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, PressureUnit) -> Value
) = byDividing(force, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.pressure

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.dynamicViscosity.dynamicViscosity
import com.splendo.kaluga.scientific.unit.ImperialPressure
import com.splendo.kaluga.scientific.unit.MetricPressure
import com.splendo.kaluga.scientific.unit.Pascal
import com.splendo.kaluga.scientific.unit.Pressure
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.UKImperialPressure
import com.splendo.kaluga.scientific.unit.USCustomaryPressure
import com.splendo.kaluga.scientific.unit.x
import kotlin.jvm.JvmName

@JvmName("metricPressureTimesTime")
infix operator fun <PressureUnit : MetricPressure, TimeUnit : Time> ScientificValue<PhysicalQuantity.Pressure, PressureUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit x time.unit).dynamicViscosity(this, time)

@JvmName("imperialPressureTimesTime")
infix operator fun <PressureUnit : ImperialPressure, TimeUnit : Time> ScientificValue<PhysicalQuantity.Pressure, PressureUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit x time.unit).dynamicViscosity(this, time)

@JvmName("ukImperialPressureTimesTime")
infix operator fun <PressureUnit : UKImperialPressure, TimeUnit : Time> ScientificValue<PhysicalQuantity.Pressure, PressureUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit x time.unit).dynamicViscosity(this, time)

@JvmName("usCustomaryPressureTimesTime")
infix operator fun <PressureUnit : USCustomaryPressure, TimeUnit : Time> ScientificValue<PhysicalQuantity.Pressure, PressureUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit x time.unit).dynamicViscosity(this, time)

@JvmName("pressureTimesTime")
infix operator fun <PressureUnit : Pressure, TimeUnit : Time> ScientificValue<PhysicalQuantity.Pressure, PressureUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (Pascal x time.unit).dynamicViscosity(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.pressure

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.energy.energy
import com.splendo.kaluga.scientific.unit.Barye
import com.splendo.kaluga.scientific.unit.CubicCentimeter
import com.splendo.kaluga.scientific.unit.CubicFoot
import com.splendo.kaluga.scientific.unit.CubicInch
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.FootPoundForce
import com.splendo.kaluga.scientific.unit.ImperialPressure
import com.splendo.kaluga.scientific.unit.ImperialTonSquareInch
import com.splendo.kaluga.scientific.unit.ImperialVolume
import com.splendo.kaluga.scientific.unit.InchOunceForce
import com.splendo.kaluga.scientific.unit.InchPoundForce
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.KiloPoundSquareInch
import com.splendo.kaluga.scientific.unit.KipSquareInch
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.OunceSquareInch
import com.splendo.kaluga.scientific.unit.PoundSquareFoot
import com.splendo.kaluga.scientific.unit.PoundSquareInch
import com.splendo.kaluga.scientific.unit.Pressure
import com.splendo.kaluga.scientific.unit.UKImperialPressure
import com.splendo.kaluga.scientific.unit.UKImperialVolume
import com.splendo.kaluga.scientific.unit.USCustomaryPressure
import com.splendo.kaluga.scientific.unit.USCustomaryVolume
import com.splendo.kaluga.scientific.unit.USTonSquareInch
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("baryeTimesCubicCentimeter")
infix operator fun ScientificValue<PhysicalQuantity.Pressure, Barye>.times(volume: ScientificValue<PhysicalQuantity.Volume, CubicCentimeter>) =
    Erg.energy(this, volume)

@JvmName("baryeMultipleTimesCubicCentimeter")
infix operator fun <BaryeUnit> ScientificValue<PhysicalQuantity.Pressure, BaryeUnit>.times(volume: ScientificValue<PhysicalQuantity.Volume, CubicCentimeter>) where BaryeUnit : Pressure, BaryeUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> =
    Erg.energy(this, volume)

@JvmName("poundSquareFootTimesCubicFoot")
infix operator fun ScientificValue<PhysicalQuantity.Pressure, PoundSquareFoot>.times(volume: ScientificValue<PhysicalQuantity.Volume, CubicFoot>) =
    FootPoundForce.energy(this, volume)

@JvmName("poundSquareInchTimesCubicInch")
infix operator fun ScientificValue<PhysicalQuantity.Pressure, PoundSquareInch>.times(volume: ScientificValue<PhysicalQuantity.Volume, CubicInch>) =
    InchPoundForce.energy(this, volume)

@JvmName("ounceSquareInchTimesCubicInch")
infix operator fun ScientificValue<PhysicalQuantity.Pressure, OunceSquareInch>.times(volume: ScientificValue<PhysicalQuantity.Volume, CubicInch>) =
    InchOunceForce.energy(this, volume)

@JvmName("kilopoundSquareInchTimesCubicInch")
infix operator fun ScientificValue<PhysicalQuantity.Pressure, KiloPoundSquareInch>.times(volume: ScientificValue<PhysicalQuantity.Volume, CubicInch>) =
    InchPoundForce.energy(this, volume)

@JvmName("kipSquareInchTimesCubicInch")
infix operator fun ScientificValue<PhysicalQuantity.Pressure, KipSquareInch>.times(volume: ScientificValue<PhysicalQuantity.Volume, CubicInch>) =
    InchPoundForce.energy(this, volume)

@JvmName("usTonSquareInchTimesCubicInch")
infix operator fun ScientificValue<PhysicalQuantity.Pressure, USTonSquareInch>.times(volume: ScientificValue<PhysicalQuantity.Volume, CubicInch>) =
    InchPoundForce.energy(this, volume)

@JvmName("imperialTonSquareInchTimesCubicInch")
infix operator fun ScientificValue<PhysicalQuantity.Pressure, ImperialTonSquareInch>.times(volume: ScientificValue<PhysicalQuantity.Volume, CubicInch>) =
    InchPoundForce.energy(this, volume)

@JvmName("imperialPressureTimesImperialVolume")
infix operator fun <PressureUnit : ImperialPressure, VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Pressure, PressureUnit>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = FootPoundForce.energy(this, volume)

@JvmName("imperialPressureTimesUKImperialVolume")
infix operator fun <PressureUnit : ImperialPressure, VolumeUnit : UKImperialVolume> ScientificValue<PhysicalQuantity.Pressure, PressureUnit>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = FootPoundForce.energy(this, volume)

@JvmName("imperialPressureTimesUSCustomaryVolume")
infix operator fun <PressureUnit : ImperialPressure, VolumeUnit : USCustomaryVolume> ScientificValue<PhysicalQuantity.Pressure, PressureUnit>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = FootPoundForce.energy(this, volume)

@JvmName("ukImperialPressureTimesImperialVolume")
infix operator fun <PressureUnit : UKImperialPressure, VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Pressure, PressureUnit>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = FootPoundForce.energy(this, volume)

@JvmName("ukImperialPressureTimesUKImperialVolume")
infix operator fun <PressureUnit : UKImperialPressure, VolumeUnit : UKImperialVolume> ScientificValue<PhysicalQuantity.Pressure, PressureUnit>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = FootPoundForce.energy(this, volume)

@JvmName("usCustomaryPressureTimesImperialVolume")
infix operator fun <PressureUnit : USCustomaryPressure, VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Pressure, PressureUnit>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = FootPoundForce.energy(this, volume)

@JvmName("usCustomaryPressureTimesUSCustomaryVolume")
infix operator fun <PressureUnit : USCustomaryPressure, VolumeUnit : USCustomaryVolume> ScientificValue<PhysicalQuantity.Pressure, PressureUnit>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = FootPoundForce.energy(this, volume)

@JvmName("pressureTimesVolume")
infix operator fun <PressureUnit : Pressure, VolumeUnit : Volume> ScientificValue<PhysicalQuantity.Pressure, PressureUnit>.times(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = Joule.energy(this, volume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.pressure

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.force.force
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Barye
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialPressure
import com.splendo.kaluga.scientific.unit.ImperialTonForce
import com.splendo.kaluga.scientific.unit.ImperialTonSquareFoot
import com.splendo.kaluga.scientific.unit.ImperialTonSquareInch
import com.splendo.kaluga.scientific.unit.Kip
import com.splendo.kaluga.scientific.unit.KipSquareFoot
import com.splendo.kaluga.scientific.unit.KipSquareInch
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.Newton
import com.splendo.kaluga.scientific.unit.OunceForce
import com.splendo.kaluga.scientific.unit.OunceSquareInch
import com.splendo.kaluga.scientific.unit.PoundForce
import com.splendo.kaluga.scientific.unit.Pressure
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.UKImperialPressure
import com.splendo.kaluga.scientific.unit.USCustomaryPressure
import com.splendo.kaluga.scientific.unit.USTonSquareFoot
import com.splendo.kaluga.scientific.unit.USTonSquareInch
import com.splendo.kaluga.scientific.unit.UsTonForce
import com.splendo.kaluga.scientific.unit.ukImperial
import com.splendo.kaluga.scientific.unit.usCustomary
import kotlin.jvm.JvmName

@JvmName("baryeTimesSquareCentimeter")
infix operator fun ScientificValue<PhysicalQuantity.Pressure, Barye>.times(area: ScientificValue<PhysicalQuantity.Area, SquareCentimeter>) =
    Dyne.force(this, area)

@JvmName("baryeMultipleTimesSquareCentimeter")
infix operator fun <BaryeUnit> ScientificValue<PhysicalQuantity.Pressure, BaryeUnit>.times(area: ScientificValue<PhysicalQuantity.Area, SquareCentimeter>) where BaryeUnit : Pressure, BaryeUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> =
    Dyne.force(this, area)

@JvmName("ounceSquareInchTimesImperialArea")
infix operator fun <Area : ImperialArea> ScientificValue<PhysicalQuantity.Pressure, OunceSquareInch>.times(
    area: ScientificValue<PhysicalQuantity.Area, Area>
) = OunceForce.force(this, area)

@JvmName("kipSquareInchTimesImperialArea")
infix operator fun <Area : ImperialArea> ScientificValue<PhysicalQuantity.Pressure, KipSquareInch>.times(
    area: ScientificValue<PhysicalQuantity.Area, Area>
) = Kip.force(this, area)

@JvmName("kipSquareFeetTimesImperialArea")
infix operator fun <Area : ImperialArea> ScientificValue<PhysicalQuantity.Pressure, KipSquareFoot>.times(
    area: ScientificValue<PhysicalQuantity.Area, Area>
) = Kip.force(this, area)

@JvmName("usTonSquareInchTimesImperialArea")
infix operator fun <Area : ImperialArea> ScientificValue<PhysicalQuantity.Pressure, USTonSquareInch>.times(
    area: ScientificValue<PhysicalQuantity.Area, Area>
) = UsTonForce.force(this, area)

@JvmName("usTonSquareFeetTimesImperialArea")
infix operator fun <Area : ImperialArea> ScientificValue<PhysicalQuantity.Pressure, USTonSquareFoot>.times(
    area: ScientificValue<PhysicalQuantity.Area, Area>
) = UsTonForce.force(this, area)

@JvmName("imperialTonSquareInchTimesImperialArea")
infix operator fun <Area : ImperialArea> ScientificValue<PhysicalQuantity.Pressure, ImperialTonSquareInch>.times(
    area: ScientificValue<PhysicalQuantity.Area, Area>
) = ImperialTonForce.force(this, area)

@JvmName("imperialTonSquareFeetTimesImperialArea")
infix operator fun <Area : ImperialArea> ScientificValue<PhysicalQuantity.Pressure, ImperialTonSquareFoot>.times(
    area: ScientificValue<PhysicalQuantity.Area, Area>
) = ImperialTonForce.force(this, area)

@JvmName("imperialPressureTimesImperialArea")
infix operator fun <Pressure : ImperialPressure, Area : ImperialArea> ScientificValue<PhysicalQuantity.Pressure, Pressure>.times(
    area: ScientificValue<PhysicalQuantity.Area, Area>
) = PoundForce.force(this, area)

@JvmName("ukImperialPressureTimesImperialArea")
infix operator fun <Pressure : UKImperialPressure, Area : ImperialArea> ScientificValue<PhysicalQuantity.Pressure, Pressure>.times(
    area: ScientificValue<PhysicalQuantity.Area, Area>
) = PoundForce.ukImperial.force(this, area)

@JvmName("usCustomaryPressureTimesImperialArea")
infix operator fun <Pressure : USCustomaryPressure, Area : ImperialArea> ScientificValue<PhysicalQuantity.Pressure, Pressure>.times(
    area: ScientificValue<PhysicalQuantity.Area, Area>
) = PoundForce.usCustomary.force(this, area)

@JvmName("pressureTimesArea")
infix operator fun <PressureUnit : Pressure, AreaUnit : Area> ScientificValue<PhysicalQuantity.Pressure, PressureUnit>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Newton.force(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.radioactivity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.base.utils.toDecimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.AvogadroConstant
import com.splendo.kaluga.scientific.unit.Radioactivity
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName
import kotlin.math.ln

@JvmName("radioactivityFromSubstanceAndHalfLifeDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    TimeUnit : Time,
    RadioactivityUnit : Radioactivity
    > RadioactivityUnit.radioactivity(
    substance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    halfLife: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = radioactivity(substance, halfLife, ::DefaultScientificValue)

@JvmName("radioactivityFromSubstanceAndHalfLife")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    TimeUnit : Time,
    RadioactivityUnit : Radioactivity,
    Value : ScientificValue<PhysicalQuantity.Radioactivity, RadioactivityUnit>
    > RadioactivityUnit.radioactivity(
    substance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    halfLife: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, RadioactivityUnit) -> Value
) = byDividing(
    DefaultScientificValue(
        substance.decimalValue * AvogadroConstant * ln(2.0).toDecimal(),
        substance.unit
    ),
    halfLife,
    factory
)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.radioactivity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byInverting
import com.splendo.kaluga.scientific.convert
import com.splendo.kaluga.scientific.convertValue
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.Becquerel
import com.splendo.kaluga.scientific.unit.Radioactivity
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("radioactivityFromInvertedTimeDefault")
fun <
    TimeUnit : Time,
    RadioactivityUnit : Radioactivity
    > RadioactivityUnit.radioactivity(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = radioactivity(time, ::DefaultScientificValue)

@JvmName("radioactivityFromInvertedTime")
fun <
    TimeUnit : Time,
    RadioactivityUnit : Radioactivity,
    Value : ScientificValue<PhysicalQuantity.Radioactivity, RadioactivityUnit>
    > RadioactivityUnit.radioactivity(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, RadioactivityUnit) -> Value
) = byInverting(time, factory)

fun <
    RadioactivityUnit : Radioactivity,
    TimeUnit : Time
    > RadioactivityUnit.radioactivity(
    decays: Decimal,
    per: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = radioactivity(decays, per, ::DefaultScientificValue)

fun <
    RadioactivityUnit : Radioactivity,
    TimeUnit : Time,
    Value : ScientificValue<PhysicalQuantity.Radioactivity, RadioactivityUnit>
    > RadioactivityUnit.radioactivity(
    decays: Decimal,
    per: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, RadioactivityUnit) -> Value
) = (decays / per.convertValue(Second))(Becquerel).convert(this, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.radioactivity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.times
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.convertValue
import com.splendo.kaluga.scientific.unit.Becquerel
import com.splendo.kaluga.scientific.unit.Radioactivity
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("radioactivityTimesTime")
infix operator fun <RadioactivityUnit : Radioactivity, TimeUnit : Time> ScientificValue<PhysicalQuantity.Radioactivity, RadioactivityUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
): Decimal = convertValue(
    Becquerel
) * time.convertValue(Second)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.solidAngle

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.Luminance
import com.splendo.kaluga.scientific.unit.SolidAngle
import kotlin.jvm.JvmName

@JvmName("solidAngleFromIlluminanceAndLuminanceDefault")
fun <
    LuminanceUnit : Luminance,
    SolidAngleUnit : SolidAngle,
    IlluminanceUnit : Illuminance
    > SolidAngleUnit.solidAngle(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>,
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>
) = solidAngle(illuminance, luminance, ::DefaultScientificValue)

@JvmName("solidAngleFromIlluminanceAndLuminance")
fun <
    LuminanceUnit : Luminance,
    SolidAngleUnit : SolidAngle,
    IlluminanceUnit : Illuminance,
    Value : ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
    > SolidAngleUnit.solidAngle(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>,
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>,
    factory: (Decimal, SolidAngleUnit) -> Value
) = byDividing(illuminance, luminance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.solidAngle

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.LuminousFlux
import com.splendo.kaluga.scientific.unit.LuminousIntensity
import com.splendo.kaluga.scientific.unit.SolidAngle
import kotlin.jvm.JvmName

@JvmName("solidAngleFromLuminousFluxAndIntensityDefault")
fun <
    IntensityUnit : LuminousIntensity,
    SolidAngleUnit : SolidAngle,
    FluxUnit : LuminousFlux
    > SolidAngleUnit.solidAngle(
    flux: ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>,
    intensity: ScientificValue<PhysicalQuantity.LuminousIntensity, IntensityUnit>
) = solidAngle(flux, intensity, ::DefaultScientificValue)

@JvmName("solidAngleFromLuminousFluxAndIntensity")
fun <
    IntensityUnit : LuminousIntensity,
    SolidAngleUnit : SolidAngle,
    FluxUnit : LuminousFlux,
    Value : ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>
    > SolidAngleUnit.solidAngle(
    flux: ScientificValue<PhysicalQuantity.LuminousFlux, FluxUnit>,
    intensity: ScientificValue<PhysicalQuantity.LuminousIntensity, IntensityUnit>,
    factory: (Decimal, SolidAngleUnit) -> Value
) = byDividing(flux, intensity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.solidAngle

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.luminance.times
import com.splendo.kaluga.scientific.unit.ImperialLuminance
import com.splendo.kaluga.scientific.unit.Lambert
import com.splendo.kaluga.scientific.unit.Luminance
import com.splendo.kaluga.scientific.unit.MetricLuminance
import com.splendo.kaluga.scientific.unit.SolidAngle
import com.splendo.kaluga.scientific.unit.Stilb
import kotlin.jvm.JvmName

@JvmName("solidAngleTimesStilb")
infix operator fun <SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>.times(
    luminance: ScientificValue<PhysicalQuantity.Luminance, Stilb>
) = luminance * this

@JvmName("solidAngleTimesLambertTimes")
infix operator fun <SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>.times(
    luminance: ScientificValue<PhysicalQuantity.Luminance, Lambert>
) = luminance * this

@JvmName("solidAngleTimesMetricLuminance")
infix operator fun <LuminanceUnit : MetricLuminance, SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>.times(
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>
) = luminance * this

@JvmName("solidAngleTimesImperialLuminance")
infix operator fun <LuminanceUnit : ImperialLuminance, SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>.times(
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>
) = luminance * this

@JvmName("solidAngleTimesLuminance")
infix operator fun <LuminanceUnit : Luminance, SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>.times(
    luminance: ScientificValue<PhysicalQuantity.Luminance, LuminanceUnit>
) = luminance * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.solidAngle

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.luminousIntensity.times
import com.splendo.kaluga.scientific.unit.LuminousIntensity
import com.splendo.kaluga.scientific.unit.SolidAngle
import kotlin.jvm.JvmName

@JvmName("solidAngleTimesLuminousIntensity")
infix operator fun <IntensityUnit : LuminousIntensity, SolidAngleUnit : SolidAngle> ScientificValue<PhysicalQuantity.SolidAngle, SolidAngleUnit>.times(
    intensity: ScientificValue<PhysicalQuantity.LuminousIntensity, IntensityUnit>
) = intensity * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificEnergy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("specificEnergyFromEnergyAndWeightDefault")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    SpecificEnergyUnit : SpecificEnergy
    > SpecificEnergyUnit.specificEnergy(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = specificEnergy(energy, weight, ::DefaultScientificValue)

@JvmName("specificEnergyFromEnergyAndWeight")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    SpecificEnergyUnit : SpecificEnergy,
    Value : ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>
    > SpecificEnergyUnit.specificEnergy(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    factory: (Decimal, SpecificEnergyUnit) -> Value
) = byDividing(energy, weight, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificEnergy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.convert
import com.splendo.kaluga.scientific.unit.Gray
import com.splendo.kaluga.scientific.unit.IonizingRadiationAbsorbedDose
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("specificEnergyFromAbsorbedDoseDefault")
fun <
    AbsorbedDoseUnit : IonizingRadiationAbsorbedDose,
    SpecificEnergyUnit : SpecificEnergy
    > SpecificEnergyUnit.specificEnergy(
    absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>
) = specificEnergy(absorbedDose, ::DefaultScientificValue)

@JvmName("specificEnergyFromAbsorbedDose")
fun <
    AbsorbedDoseUnit : IonizingRadiationAbsorbedDose,
    SpecificEnergyUnit : SpecificEnergy,
    Value : ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>
    > SpecificEnergyUnit.specificEnergy(
    absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>,
    factory: (Decimal, SpecificEnergyUnit) -> Value
) = DefaultScientificValue(absorbedDose.convert(Gray).value, Joule per Kilogram).convert(
    this,
    factory
)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificEnergy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.convert
import com.splendo.kaluga.scientific.unit.IonizingRadiationEquivalentDose
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.Sievert
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("specificEnergyFromEquivalentDoseDefault")
fun <
    EquivalentDoseUnit : IonizingRadiationEquivalentDose,
    SpecificEnergyUnit : SpecificEnergy
    > SpecificEnergyUnit.specificEnergy(
    equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>
) = specificEnergy(equivalentDose, ::DefaultScientificValue)

@JvmName("specificEnergyFromEquivalentDose")
fun <
    EquivalentDoseUnit : IonizingRadiationEquivalentDose,
    SpecificEnergyUnit : SpecificEnergy,
    Value : ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>
    > SpecificEnergyUnit.specificEnergy(
    equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>,
    factory: (Decimal, SpecificEnergyUnit) -> Value
) = DefaultScientificValue(equivalentDose.convert(Sievert).value, Joule per Kilogram).convert(
    this,
    factory
)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificEnergy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarEnergy
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import kotlin.jvm.JvmName

@JvmName("specificEnergyFromMolarEnergyAndMolalityDefault")
fun <
    SpecificEnergyUnit : SpecificEnergy,
    MolarEnergyUnit : MolarEnergy,
    MolalityUnit : Molality
    > SpecificEnergyUnit.specificEnergy(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>,
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = specificEnergy(molarEnergy, molality, ::DefaultScientificValue)

@JvmName("specificEnergyFromMolarEnergyAndMolality")
fun <
    SpecificEnergyUnit : SpecificEnergy,
    MolarEnergyUnit : MolarEnergy,
    MolalityUnit : Molality,
    Value : ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>
    > SpecificEnergyUnit.specificEnergy(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>,
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>,
    factory: (Decimal, SpecificEnergyUnit) -> Value
) = byMultiplying(molarEnergy, molality, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificEnergy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.MolarEnergy
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import kotlin.jvm.JvmName

@JvmName("specificEnergyFromMolarEnergyAndMolarMassDefault")
fun <
    MolarEnergyUnit : MolarEnergy,
    MolarMassUnit : MolarMass,
    SpecificEnergyUnit : SpecificEnergy
    > SpecificEnergyUnit.specificEnergy(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>,
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = specificEnergy(molarEnergy, molarMass, ::DefaultScientificValue)

@JvmName("specificEnergyFromMolarEnergyAndMolarMass")
fun <
    MolarEnergyUnit : MolarEnergy,
    MolarMassUnit : MolarMass,
    SpecificEnergyUnit : SpecificEnergy,
    Value : ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>
    > SpecificEnergyUnit.specificEnergy(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>,
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    factory: (Decimal, SpecificEnergyUnit) -> Value
) = byDividing(molarEnergy, molarMass, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificEnergy

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.converter.temperature.deltaValue
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.SpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.Temperature
import kotlin.jvm.JvmName

@JvmName("specificEnergyFromSpecificHeatCapacityAndTemperatureDefault")
fun <
    SpecificEnergyUnit : SpecificEnergy,
    TemperatureUnit : Temperature,
    SpecificHeatCapacityUnit : SpecificHeatCapacity
    > SpecificEnergyUnit.specificEnergy(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>,
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = specificEnergy(specificHeatCapacity, temperature, ::DefaultScientificValue)

@JvmName("specificEnergyFromSpecificHeatCapacityAndTemperature")
fun <
    SpecificEnergyUnit : SpecificEnergy,
    TemperatureUnit : Temperature,
    SpecificHeatCapacityUnit : SpecificHeatCapacity,
    Value : ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>
    > SpecificEnergyUnit.specificEnergy(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>,
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>,
    factory: (Decimal, SpecificEnergyUnit) -> Value
) = byMultiplying(specificHeatCapacity, Kelvin.deltaValue(temperature), factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificEnergy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.energy.energy
import com.splendo.kaluga.scientific.unit.ImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.MetricSpecificEnergy
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.UKImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomarySpecificEnergy
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("metricSpecificEnergyTimesMetricWeight")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.SpecificEnergy, MetricSpecificEnergy>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit.energy).energy(this, weight)

@JvmName("imperialSpecificEnergyTimesImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit.energy).energy(this, weight)

@JvmName("imperialSpecificEnergyTimesUKImperialWeight")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit.energy).energy(this, weight)

@JvmName("imperialSpecificEnergyTimesUSCustomaryWeight")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit.energy).energy(this, weight)

@JvmName("ukImperialSpecificEnergyTimesImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.SpecificEnergy, UKImperialSpecificEnergy>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit.energy).energy(this, weight)

@JvmName("ukImperialSpecificEnergyTimesUKImperialWeight")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.SpecificEnergy, UKImperialSpecificEnergy>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit.energy).energy(this, weight)

@JvmName("usCustomarySpecificEnergyTimesImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.SpecificEnergy, USCustomarySpecificEnergy>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit.energy).energy(this, weight)

@JvmName("usCustomarySpecificEnergyTimesUSCustomaryWeight")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.SpecificEnergy, USCustomarySpecificEnergy>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit.energy).energy(this, weight)

@JvmName("specificEnergyTimesWeight")
infix operator fun <SpecificEnergyUnit : SpecificEnergy, WeightUnit : Weight> ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit.energy).energy(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificEnergy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.ionizingRadiationAbsorbedDose.absorbedDose
import com.splendo.kaluga.scientific.unit.Gray
import com.splendo.kaluga.scientific.unit.SpecificEnergy

fun <SpecificEnergyUnit : SpecificEnergy> ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>.asAbsorbedDose() =
    Gray.absorbedDose(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificEnergy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.ionizingRadiationEquivalentDose.equivalentDose
import com.splendo.kaluga.scientific.unit.Sievert
import com.splendo.kaluga.scientific.unit.SpecificEnergy

fun <SpecificEnergyUnit : SpecificEnergy> ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>.asEquivalentDose() =
    Sievert.equivalentDose(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificEnergy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molality.molality
import com.splendo.kaluga.scientific.unit.ImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricSpecificEnergy
import com.splendo.kaluga.scientific.unit.MolarEnergy
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.UKImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.USCustomarySpecificEnergy
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricSpecificEnergyDivMolarEnergy")
infix operator fun <MolarEnergyUnit : MolarEnergy> ScientificValue<PhysicalQuantity.SpecificEnergy, MetricSpecificEnergy>.div(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>
) = (molarEnergy.unit.per per unit.per).molality(this, molarEnergy)

@JvmName("imperialSpecificEnergyDivMolarEnergy")
infix operator fun <MolarEnergyUnit : MolarEnergy> ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>.div(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>
) = (molarEnergy.unit.per per unit.per).molality(this, molarEnergy)

@JvmName("ukImperialSpecificEnergyDivMolarEnergy")
infix operator fun <MolarEnergyUnit : MolarEnergy> ScientificValue<PhysicalQuantity.SpecificEnergy, UKImperialSpecificEnergy>.div(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>
) = (molarEnergy.unit.per per unit.per).molality(this, molarEnergy)

@JvmName("usCustomarySpecificEnergyDivMolarEnergy")
infix operator fun <MolarEnergyUnit : MolarEnergy> ScientificValue<PhysicalQuantity.SpecificEnergy, USCustomarySpecificEnergy>.div(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>
) = (molarEnergy.unit.per per unit.per).molality(this, molarEnergy)

@JvmName("specificEnergyDivMolarEnergy")
infix operator fun <SpecificEnergyUnit : SpecificEnergy, MolarEnergyUnit : MolarEnergy> ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>.div(
    molarEnergy: ScientificValue<PhysicalQuantity.MolarEnergy, MolarEnergyUnit>
) = (molarEnergy.unit.per per Kilogram).molality(this, molarEnergy)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificEnergy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarEnergy.molarEnergy
import com.splendo.kaluga.scientific.unit.ImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.MetricSpecificEnergy
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.UKImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.USCustomarySpecificEnergy
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricSpecificEnergyTimesMolarMass")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.SpecificEnergy, MetricSpecificEnergy>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = (unit.energy per molarMass.unit.per).molarEnergy(this, molarMass)

@JvmName("imperialSpecificEnergyTimesMolarMass")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = (unit.energy per molarMass.unit.per).molarEnergy(this, molarMass)

@JvmName("ukImperialSpecificEnergyTimesMolarMass")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.SpecificEnergy, UKImperialSpecificEnergy>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = (unit.energy per molarMass.unit.per).molarEnergy(this, molarMass)

@JvmName("usCustomarySpecificEnergyTimesMolarMass")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.SpecificEnergy, USCustomarySpecificEnergy>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = (unit.energy per molarMass.unit.per).molarEnergy(this, molarMass)

@JvmName("specificEnergyTimesMolarMass")
infix operator fun <SpecificEnergyUnit : SpecificEnergy, MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = (Joule per molarMass.unit.per).molarEnergy(this, molarMass)

@JvmName("metricSpecificEnergyDivMolality")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.SpecificEnergy, MetricSpecificEnergy>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = (unit.energy per molality.unit.amountOfSubstance).molarEnergy(this, molality)

@JvmName("imperialSpecificEnergyDivMolality")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = (unit.energy per molality.unit.amountOfSubstance).molarEnergy(this, molality)

@JvmName("ukImperialSpecificEnergyDivMolality")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.SpecificEnergy, UKImperialSpecificEnergy>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = (unit.energy per molality.unit.amountOfSubstance).molarEnergy(this, molality)

@JvmName("usCustomarySpecificEnergyDivMolality")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.SpecificEnergy, USCustomarySpecificEnergy>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = (unit.energy per molality.unit.amountOfSubstance).molarEnergy(this, molality)

@JvmName("specificEnergyDivMolality")
infix operator fun <SpecificEnergyUnit : SpecificEnergy, MolalityUnit : Molality> ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = (Joule per molality.unit.amountOfSubstance).molarEnergy(this, molality)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificEnergy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.specificHeatCapacity.specificHeatCapacity
import com.splendo.kaluga.scientific.unit.ImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialTemperature
import com.splendo.kaluga.scientific.unit.MetricSpecificEnergy
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.Temperature
import com.splendo.kaluga.scientific.unit.UKImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.USCustomarySpecificEnergy
import com.splendo.kaluga.scientific.unit.USCustomaryTemperature
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricSpecificEnergyDivMetricAndUKImperialTemperature")
infix operator fun ScientificValue<PhysicalQuantity.SpecificEnergy, MetricSpecificEnergy>.div(
    temperature: ScientificValue<PhysicalQuantity.Temperature, MetricAndUKImperialTemperature>
) = (unit per temperature.unit).specificHeatCapacity(this, temperature)

@JvmName("imperialSpecificEnergyDivMetricAndUKImperialTemperature")
infix operator fun ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>.div(
    temperature: ScientificValue<PhysicalQuantity.Temperature, MetricAndUKImperialTemperature>
) = (unit per temperature.unit).specificHeatCapacity(this, temperature)

@JvmName("imperialSpecificEnergyDivUSCustomaryTemperature")
infix operator fun ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>.div(
    temperature: ScientificValue<PhysicalQuantity.Temperature, USCustomaryTemperature>
) = (unit per temperature.unit).specificHeatCapacity(this, temperature)

@JvmName("ukImperialSpecificEnergyDivMetricAndUKImperialTemperature")
infix operator fun ScientificValue<PhysicalQuantity.SpecificEnergy, UKImperialSpecificEnergy>.div(
    temperature: ScientificValue<PhysicalQuantity.Temperature, MetricAndUKImperialTemperature>
) = (unit per temperature.unit).specificHeatCapacity(this, temperature)

@JvmName("usCustomarySpecificEnergyDivUSCustomaryTemperature")
infix operator fun ScientificValue<PhysicalQuantity.SpecificEnergy, USCustomarySpecificEnergy>.div(
    temperature: ScientificValue<PhysicalQuantity.Temperature, USCustomaryTemperature>
) = (unit per temperature.unit).specificHeatCapacity(this, temperature)

@JvmName("specificEnergyDivTemperature")
infix operator fun <SpecificEnergyUnit : SpecificEnergy, TemperatureUnit : Temperature> ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>.div(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (Joule per Kelvin per Kilogram).specificHeatCapacity(this, temperature)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificEnergy

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.temperature.temperature
import com.splendo.kaluga.scientific.unit.ImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.MetricSpecificEnergy
import com.splendo.kaluga.scientific.unit.MetricSpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.SpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.UKImperialSpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificEnergy
import com.splendo.kaluga.scientific.unit.USCustomarySpecificHeatCapacity
import kotlin.jvm.JvmName

@JvmName("metricSpecificEnergyDivMetricSpecificHeatCapacity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificEnergy, MetricSpecificEnergy>.div(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, MetricSpecificHeatCapacity>
) = specificHeatCapacity.unit.heatCapacity.per.temperature(this, specificHeatCapacity)

@JvmName("imperialSpecificEnergyDivUKImperialSpecificHeatCapacity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>.div(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, UKImperialSpecificHeatCapacity>
) = specificHeatCapacity.unit.heatCapacity.per.temperature(this, specificHeatCapacity)

@JvmName("imperialSpecificEnergyDivUSCustomarySpecificHeatCapacity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>.div(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, USCustomarySpecificHeatCapacity>
) = specificHeatCapacity.unit.heatCapacity.per.temperature(this, specificHeatCapacity)

@JvmName("ukImperialSpecificEnergyDivUKImperialSpecificHeatCapacity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificEnergy, UKImperialSpecificEnergy>.div(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, UKImperialSpecificHeatCapacity>
) = specificHeatCapacity.unit.heatCapacity.per.temperature(this, specificHeatCapacity)

@JvmName("usCustomarySpecificEnergyDivUSCustomarySpecificHeatCapacity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificEnergy, USCustomarySpecificEnergy>.div(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, USCustomarySpecificHeatCapacity>
) = specificHeatCapacity.unit.heatCapacity.per.temperature(this, specificHeatCapacity)

@JvmName("specificEnergyDivTemperature")
infix operator fun <SpecificEnergyUnit : SpecificEnergy, SpecificHeatCapacityUnit : SpecificHeatCapacity> ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>.div(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>
) = Kelvin.temperature(this, specificHeatCapacity)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificHeatCapacity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.HeatCapacity
import com.splendo.kaluga.scientific.unit.SpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("specificHeatCapacityFromHeatCapacityAndWeightDefault")
fun <
    HeatCapacityUnit : HeatCapacity,
    WeightUnit : Weight,
    SpecificHeatCapacityUnit : SpecificHeatCapacity
    > SpecificHeatCapacityUnit.specificHeatCapacity(
    heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, HeatCapacityUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = specificHeatCapacity(heatCapacity, weight, ::DefaultScientificValue)

@JvmName("specificHeatCapacityFromHeatCapacityAndWeight")
fun <
    HeatCapacityUnit : HeatCapacity,
    WeightUnit : Weight,
    SpecificHeatCapacityUnit : SpecificHeatCapacity,
    Value : ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>
    > SpecificHeatCapacityUnit.specificHeatCapacity(
    heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, HeatCapacityUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    factory: (Decimal, SpecificHeatCapacityUnit) -> Value
) = byDividing(heatCapacity, weight, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificHeatCapacity

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.converter.temperature.deltaValue
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.SpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.Temperature
import kotlin.jvm.JvmName

@JvmName("specificHeatCapacityFromSpecificEnergyAndTemperatureDefault")
fun <
    SpecificEnergyUnit : SpecificEnergy,
    TemperatureUnit : Temperature,
    SpecificHeatCapacityUnit : SpecificHeatCapacity
    > SpecificHeatCapacityUnit.specificHeatCapacity(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = specificHeatCapacity(specificEnergy, temperature, ::DefaultScientificValue)

@JvmName("specificHeatCapacityFromSpecificEnergyAndTemperature")
fun <
    SpecificEnergyUnit : SpecificEnergy,
    TemperatureUnit : Temperature,
    SpecificHeatCapacityUnit : SpecificHeatCapacity,
    Value : ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>
    > SpecificHeatCapacityUnit.specificHeatCapacity(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>,
    factory: (Decimal, SpecificHeatCapacityUnit) -> Value
) = byDividing(specificEnergy, Kelvin.deltaValue(temperature), factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificHeatCapacity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.heatCapacity.heatCapacity
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.MetricSpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.SpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomarySpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricSpecificHeatCapacityTimesMetricWeight")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.SpecificHeatCapacity, MetricSpecificHeatCapacity>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.heatCapacity.heatCapacity(this, weight)

@JvmName("ukImperialSpecificHeatCapacityTimesImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.SpecificHeatCapacity, UKImperialSpecificHeatCapacity>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.heatCapacity.heatCapacity(this, weight)

@JvmName("ukImperialSpecificHeatCapacityTimesUKImperialWeight")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.SpecificHeatCapacity, UKImperialSpecificHeatCapacity>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.heatCapacity.heatCapacity(this, weight)

@JvmName("usCustomarySpecificHeatCapacityTimesImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.SpecificHeatCapacity, USCustomarySpecificHeatCapacity>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.heatCapacity.heatCapacity(this, weight)

@JvmName("usCustomarySpecificHeatCapacityTimesUSCustomaryWeight")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.SpecificHeatCapacity, USCustomarySpecificHeatCapacity>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.heatCapacity.heatCapacity(this, weight)

@JvmName("specificHeatCapacityTimesWeight")
infix operator fun <SpecificHeatCapacityUnit : SpecificHeatCapacity, WeightUnit : Weight> ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (Joule per Kelvin).heatCapacity(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificHeatCapacity

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.specificEnergy.specificEnergy
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialTemperature
import com.splendo.kaluga.scientific.unit.MetricSpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.SpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.Temperature
import com.splendo.kaluga.scientific.unit.UKImperialSpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.USCustomaryTemperature
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricSpecificHeatCapacityTimesMetricAndUKImperialTemperature")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.SpecificHeatCapacity, MetricSpecificHeatCapacity>.times(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (unit.heatCapacity.energy per unit.perWeight).specificEnergy(this, temperature)

@JvmName("ukImperialSpecificHeatCapacityTimesMetricAndUKImperialTemperature")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.SpecificHeatCapacity, UKImperialSpecificHeatCapacity>.times(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (unit.heatCapacity.energy per unit.perWeight).specificEnergy(this, temperature)

@JvmName("usCustomarySpecificHeatCapacityTimesUSCustomaryTemperature")
infix operator fun <TemperatureUnit : USCustomaryTemperature> ScientificValue<PhysicalQuantity.SpecificHeatCapacity, USCustomarySpecificHeatCapacity>.times(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (unit.heatCapacity.energy per unit.perWeight).specificEnergy(this, temperature)

@JvmName("heatCapacityTimesTemperature")
infix operator fun <SpecificHeatCapacityUnit : SpecificHeatCapacity, TemperatureUnit : Temperature> ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>.times(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
) = (Joule per Kilogram).specificEnergy(this, temperature)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificVolume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("specificVolumeFromAreaAndLinearMassDensityDefault")
fun <
    SpecificVolumeUnit : SpecificVolume,
    AreaUnit : Area,
    LinearMassDensityUnit : LinearMassDensity
    > SpecificVolumeUnit.specificVolume(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>
) = specificVolume(area, linearMassDensity, ::DefaultScientificValue)

@JvmName("specificVolumeFromAreaAndLinearMassDensity")
fun <
    SpecificVolumeUnit : SpecificVolume,
    AreaUnit : Area,
    LinearMassDensityUnit : LinearMassDensity,
    Value : ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
    > SpecificVolumeUnit.specificVolume(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>,
    factory: (Decimal, SpecificVolumeUnit) -> Value
) = byDividing(area, linearMassDensity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificVolume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byInverting
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("specificVolumeFromInvertedDensityDefault")
fun <
    SpecificVolumeUnit : SpecificVolume,
    DensityUnit : Density
    > SpecificVolumeUnit.specificVolume(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = specificVolume(density, ::DefaultScientificValue)

@JvmName("specificVolumeFromInvertedDensity")
fun <
    SpecificVolumeUnit : SpecificVolume,
    DensityUnit : Density,
    Value : ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
    > SpecificVolumeUnit.specificVolume(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    factory: (Decimal, SpecificVolumeUnit) -> Value
) = byInverting(density, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificVolume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("specificVolumeFromLengthAndAreaDensityDefault")
fun <
    SpecificVolumeUnit : SpecificVolume,
    LengthUnit : Length,
    AreaDensityUnit : AreaDensity
    > SpecificVolumeUnit.specificVolume(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>
) = specificVolume(length, areaDensity, ::DefaultScientificValue)

@JvmName("specificVolumeFromLengthAndAreaDensity")
fun <
    SpecificVolumeUnit : SpecificVolume,
    LengthUnit : Length,
    AreaDensityUnit : AreaDensity,
    Value : ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
    > SpecificVolumeUnit.specificVolume(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>,
    factory: (Decimal, SpecificVolumeUnit) -> Value
) = byDividing(length, areaDensity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificVolume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("specificVolumeFromMolalityAndMolarVolumeDefault")
fun <
    SpecificVolumeUnit : SpecificVolume,
    MolalityUnit : Molality,
    MolarVolumeUnit : MolarVolume
    > SpecificVolumeUnit.specificVolume(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>,
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = specificVolume(molarVolume, molality, ::DefaultScientificValue)

@JvmName("specificVolumeFromMolalityAndMolarVolume")
fun <
    SpecificVolumeUnit : SpecificVolume,
    MolalityUnit : Molality,
    MolarVolumeUnit : MolarVolume,
    Value : ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
    > SpecificVolumeUnit.specificVolume(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>,
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>,
    factory: (Decimal, SpecificVolumeUnit) -> Value
) = byMultiplying(molarVolume, molality, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificVolume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("specificVolumeFromMolalityAndMolarityDefault")
fun <
    MolarityUnit : Molarity,
    SpecificVolumeUnit : SpecificVolume,
    MolalityUnit : Molality
    > SpecificVolumeUnit.specificVolume(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>,
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = specificVolume(molality, molarity, ::DefaultScientificValue)

@JvmName("specificVolumeFromMolalityAndMolarity")
fun <
    MolarityUnit : Molarity,
    SpecificVolumeUnit : SpecificVolume,
    MolalityUnit : Molality,
    Value : ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
    > SpecificVolumeUnit.specificVolume(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>,
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>,
    factory: (Decimal, SpecificVolumeUnit) -> Value
) = byDividing(molality, molarity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificVolume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.SpecificVolume
import kotlin.jvm.JvmName

@JvmName("specificVolumeFromMolarVolumeAndMolarMassDefault")
fun <
    MolarVolumeUnit : MolarVolume,
    MolarMassUnit : MolarMass,
    SpecificVolumeUnit : SpecificVolume
    > SpecificVolumeUnit.specificVolume(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>,
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = specificVolume(molarVolume, molarMass, ::DefaultScientificValue)

@JvmName("specificVolumeFromMolarVolumeAndMolarMass")
fun <
    MolarVolumeUnit : MolarVolume,
    MolarMassUnit : MolarMass,
    SpecificVolumeUnit : SpecificVolume,
    Value : ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
    > SpecificVolumeUnit.specificVolume(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>,
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    factory: (Decimal, SpecificVolumeUnit) -> Value
) = byDividing(molarVolume, molarMass, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificVolume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("specificVolumeFromVolumeAndWeightDefault")
fun <
    WeightUnit : Weight,
    VolumeUnit : Volume,
    SpecificVolumeUnit : SpecificVolume
    > SpecificVolumeUnit.specificVolume(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = specificVolume(volume, weight, ::DefaultScientificValue)

@JvmName("specificVolumeFromVolumeAndWeight")
fun <
    WeightUnit : Weight,
    VolumeUnit : Volume,
    SpecificVolumeUnit : SpecificVolume,
    Value : ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
    > SpecificVolumeUnit.specificVolume(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    factory: (Decimal, SpecificVolumeUnit) -> Value
) = byDividing(volume, weight, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificVolume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.area
import com.splendo.kaluga.scientific.converter.volume.div
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.ImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import com.splendo.kaluga.scientific.unit.MetricLinearMassDensity
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.UKImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomaryLinearMassDensity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import kotlin.jvm.JvmName

@JvmName("metricSpecificVolumeTimesMetricLinearMassDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>.times(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, MetricLinearMassDensity>
) = (1(unit.volume) / 1(linearMassDensity.unit.per)).unit.area(this, linearMassDensity)

@JvmName("imperialSpecificVolumeTimesImperialLinearMassDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>.times(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>
) = (1(unit.volume) / 1(linearMassDensity.unit.per)).unit.area(this, linearMassDensity)

@JvmName("imperialSpecificVolumeTimesUKImperialLinearMassDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>.times(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, UKImperialLinearMassDensity>
) = (1(unit.volume) / 1(linearMassDensity.unit.per)).unit.area(this, linearMassDensity)

@JvmName("imperialSpecificVolumeTimesUSCustomaryLinearMassDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>.times(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, USCustomaryLinearMassDensity>
) = (1(unit.volume) / 1(linearMassDensity.unit.per)).unit.area(this, linearMassDensity)

@JvmName("ukImperialSpecificVolumeTimesImperialLinearMassDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>.times(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>
) = (1(unit.volume) / 1(linearMassDensity.unit.per)).unit.area(this, linearMassDensity)

@JvmName("ukImperialSpecificVolumeTimesUKImperialLinearMassDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>.times(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, UKImperialLinearMassDensity>
) = (1(unit.volume) / 1(linearMassDensity.unit.per)).unit.area(this, linearMassDensity)

@JvmName("usCustomarySpecificVolumeTimesImperialLinearMassDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>.times(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>
) = (1(unit.volume) / 1(linearMassDensity.unit.per)).unit.area(this, linearMassDensity)

@JvmName("usCustomarySpecificVolumeTimesUSCustomaryLinearMassDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>.times(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, USCustomaryLinearMassDensity>
) = (1(unit.volume) / 1(linearMassDensity.unit.per)).unit.area(this, linearMassDensity)

@JvmName("specificVolumeTimesLinearMassDensity")
infix operator fun <SpecificVolumeUnit : SpecificVolume, LinearMassDensityUnit : LinearMassDensity> ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>.times(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>
) = SquareMeter.area(this, linearMassDensity)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificVolume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.density.density
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricSpecificVolumeDensity")
fun ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>.density() =
    (unit.per per unit.volume).density(this)

@JvmName("imperialSpecificVolumeDensity")
fun ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>.density() =
    (unit.per per unit.volume).density(this)

@JvmName("ukImperialSpecificVolumeDensity")
fun ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>.density() =
    (unit.per per unit.volume).density(this)

@JvmName("usCustomarySpecificVolumeDensity")
fun ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>.density() =
    (unit.per per unit.volume).density(this)

@JvmName("specificVolumeDensity")
fun <SpecificVolumeUnit : SpecificVolume> ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>.density() =
    (Kilogram per CubicMeter).density(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificVolume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.length.length
import com.splendo.kaluga.scientific.converter.volume.div
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.ImperialAreaDensity
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricAreaDensity
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialAreaDensity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomaryAreaDensity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import kotlin.jvm.JvmName

@JvmName("metricSpecificVolumeTimesMetricAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, MetricAreaDensity>
) = (1(unit.volume) / 1(areaDensity.unit.per)).unit.length(this, areaDensity)

@JvmName("imperialSpecificVolumeTimesImperialAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>
) = (1(unit.volume) / 1(areaDensity.unit.per)).unit.length(this, areaDensity)

@JvmName("imperialSpecificVolumeTimesUKImperialAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>
) = (1(unit.volume) / 1(areaDensity.unit.per)).unit.length(this, areaDensity)

@JvmName("imperialSpecificVolumeTimesUSCustomaryAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>
) = (1(unit.volume) / 1(areaDensity.unit.per)).unit.length(this, areaDensity)

@JvmName("ukImperialSpecificVolumeTimesImperialAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>
) = (1(unit.volume) / 1(areaDensity.unit.per)).unit.length(this, areaDensity)

@JvmName("ukImperialSpecificVolumeTimesUKImperialAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>
) = (1(unit.volume) / 1(areaDensity.unit.per)).unit.length(this, areaDensity)

@JvmName("usCustomarySpecificVolumeTimesImperialAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>
) = (1(unit.volume) / 1(areaDensity.unit.per)).unit.length(this, areaDensity)

@JvmName("usCustomarySpecificVolumeTimesUSCustomaryAreaDensity")
infix operator fun ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>
) = (1(unit.volume) / 1(areaDensity.unit.per)).unit.length(this, areaDensity)

@JvmName("specificVolumeTimesAreaDensity")
infix operator fun <SpecificVolumeUnit : SpecificVolume, AreaDensityUnit : AreaDensity> ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>.times(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>
) = Meter.length(this, areaDensity)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificVolume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molality.molality
import com.splendo.kaluga.scientific.converter.molarity.times
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricSpecificVolumeDivMolarVolume")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>.div(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
) = (molarVolume.unit.per per unit.per).molality(this, molarVolume)

@JvmName("imperialSpecificVolumeDivMolarVolume")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>.div(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
) = (molarVolume.unit.per per unit.per).molality(this, molarVolume)

@JvmName("ukImperialSpecificVolumeDivMolarVolume")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>.div(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
) = (molarVolume.unit.per per unit.per).molality(this, molarVolume)

@JvmName("usCustomarySpecificVolumeDivMolarVolume")
infix operator fun <MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>.div(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
) = (molarVolume.unit.per per unit.per).molality(this, molarVolume)

@JvmName("specificVolumeDivMolarVolume")
infix operator fun <SpecificVolumeUnit : SpecificVolume, MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>.div(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
) = (molarVolume.unit.per per Kilogram).molality(this, molarVolume)

@JvmName("metricSpecificVolumeTimesMolarity")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>.times(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = molarity * this

@JvmName("imperialSpecificVolumeTimesMolarity")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>.times(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = molarity * this

@JvmName("ukImperialSpecificVolumeTimesMolarity")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>.times(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = molarity * this

@JvmName("usCustomarySpecificVolumeTimesMolarity")
infix operator fun <MolarityUnit : Molarity> ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>.times(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = molarity * this

@JvmName("specificVolumeTimesMolarity")
infix operator fun <MolarityUnit : Molarity, SpecificVolumeUnit : SpecificVolume> ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>.times(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = molarity * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificVolume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarMass.times
import com.splendo.kaluga.scientific.converter.molarVolume.molarVolume
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricSpecificVolumeTimesMolarMass")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = molarMass * this

@JvmName("imperialSpecificVolumeTimesMolarMass")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = molarMass * this

@JvmName("ukImperialSpecificVolumeTimesMolarMass")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = molarMass * this

@JvmName("usCustomarySpecificVolumeTimesMolarMass")
infix operator fun <MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = molarMass * this

@JvmName("specificVolumeTimesMolarMass")
infix operator fun <SpecificVolumeUnit : SpecificVolume, MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>.times(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = molarMass * this

@JvmName("metricSpecificVolumeDivMolality")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = (unit.volume per molality.unit.amountOfSubstance).molarVolume(this, molality)

@JvmName("imperialSpecificVolumeDivMolality")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = (unit.volume per molality.unit.amountOfSubstance).molarVolume(this, molality)

@JvmName("ukImperialSpecificVolumeDivMolality")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = (unit.volume per molality.unit.amountOfSubstance).molarVolume(this, molality)

@JvmName("usCustomarySpecificVolumeDivMolality")
infix operator fun <MolalityUnit : Molality> ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = (unit.volume per molality.unit.amountOfSubstance).molarVolume(this, molality)

@JvmName("specificVolumeDivMolality")
infix operator fun <SpecificVolumeUnit : SpecificVolume, MolalityUnit : Molality> ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>.div(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = (CubicMeter per molality.unit.amountOfSubstance).molarVolume(this, molality)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.specificVolume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.volume.volume
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("metricSpecificVolumeTimesMetricWeight")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.volume.volume(this, weight)

@JvmName("imperialSpecificVolumeTimesImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.volume.volume(this, weight)

@JvmName("imperialSpecificVolumeTimesUKImperialWeight")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.volume.volume(this, weight)

@JvmName("imperialSpecificVolumeTimesUSCustomaryWeight")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.volume.volume(this, weight)

@JvmName("ukImperialSpecificVolumeTimesImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.volume.volume(this, weight)

@JvmName("ukImperialSpecificVolumeTimesUKImperialWeight")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.volume.volume(this, weight)

@JvmName("usCustomarySpecificVolumeTimesUKImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.volume.volume(this, weight)

@JvmName("usCustomarySpecificVolumeTimesUSCustomaryWeight")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = unit.volume.volume(this, weight)

@JvmName("specificVolumeTimesWeight")
infix operator fun <SpecificVolumeUnit : SpecificVolume, WeightUnit : Weight> ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = CubicMeter.volume(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.speed

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("speedFromAccelerationAndTimeDefault")
fun <
    AccelerationUnit : Acceleration,
    TimeUnit : Time,
    SpeedUnit : Speed
    > SpeedUnit.speed(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = speed(acceleration, time, ::DefaultScientificValue)

@JvmName("speedFromAccelerationAndTime")
fun <
    AccelerationUnit : Acceleration,
    TimeUnit : Time,
    SpeedUnit : Speed,
    Value : ScientificValue<PhysicalQuantity.Speed, SpeedUnit>
    > SpeedUnit.speed(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, SpeedUnit) -> Value
) = byMultiplying(acceleration, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.speed

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("speedFromDistanceAndTimeDefault")
fun <
    LengthUnit : Length,
    TimeUnit : Time,
    SpeedUnit : Speed
    > SpeedUnit.speed(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = speed(distance, time, ::DefaultScientificValue)

@JvmName("speedFromDistanceAndTime")
fun <
    LengthUnit : Length,
    TimeUnit : Time,
    SpeedUnit : Speed,
    Value : ScientificValue<PhysicalQuantity.Speed, SpeedUnit>
    > SpeedUnit.speed(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, SpeedUnit) -> Value
) = byDividing(distance, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.speed

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Momentum
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("speedFromMomentumAndMassDefault")
fun <
    WeightUnit : Weight,
    SpeedUnit : Speed,
    MomentumUnit : Momentum
    > SpeedUnit.speed(
    momentum: ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>,
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = speed(momentum, mass, ::DefaultScientificValue)

@JvmName("speedFromMomentumAndMass")
fun <
    WeightUnit : Weight,
    SpeedUnit : Speed,
    MomentumUnit : Momentum,
    Value : ScientificValue<PhysicalQuantity.Speed, SpeedUnit>
    > SpeedUnit.speed(
    momentum: ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>,
    mass: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    factory: (Decimal, SpeedUnit) -> Value
) = byDividing(momentum, mass, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.speed

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Speed
import kotlin.jvm.JvmName

@JvmName("speedFromPowerAndForceDefault")
fun <
    ForceUnit : Force,
    SpeedUnit : Speed,
    PowerUnit : Power
    > SpeedUnit.speed(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>,
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = speed(power, force, ::DefaultScientificValue)

@JvmName("speedFromPowerAndForce")
fun <
    ForceUnit : Force,
    SpeedUnit : Speed,
    PowerUnit : Power,
    Value : ScientificValue<PhysicalQuantity.Speed, SpeedUnit>
    > SpeedUnit.speed(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>,
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    factory: (Decimal, SpeedUnit) -> Value
) = byDividing(power, force, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.speed

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.acceleration.acceleration
import com.splendo.kaluga.scientific.unit.ImperialSpeed
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricSpeed
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricSpeedDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Speed, MetricSpeed>.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    (unit per time.unit).acceleration(this, time)

@JvmName("imperialSpeedDivTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>.div(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    (unit per time.unit).acceleration(this, time)

@JvmName("speedDivTime")
infix operator fun <SpeedUnit : Speed, TimeUnit : Time> ScientificValue<PhysicalQuantity.Speed, SpeedUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (Meter per Second per time.unit).acceleration(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.speed

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.length.distance
import com.splendo.kaluga.scientific.unit.ImperialSpeed
import com.splendo.kaluga.scientific.unit.MetricSpeed
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("metricSpeedTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Speed, MetricSpeed>.times(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    unit.distance.distance(this, time)

@JvmName("imperialSpeedTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.distance.distance(this, time)

@JvmName("speedTimesTime")
infix operator fun <SpeedUnit : Speed, TimeUnit : Time> ScientificValue<PhysicalQuantity.Speed, SpeedUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.distance.distance(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.speed

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.weight.times
import com.splendo.kaluga.scientific.unit.ImperialSpeed
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.MetricSpeed
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("metricSpeedTimesMetricWeight")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Speed, MetricSpeed>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = weight * this

@JvmName("imperialSpeedTimesImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = weight * this

@JvmName("imperialSpeedTimesUKImperialWeight")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = weight * this

@JvmName("imperialSpeedTimesUSCustomaryWeight")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = weight * this

@JvmName("speedTimesWeight")
infix operator fun <WeightUnit : Weight, SpeedUnit : Speed> ScientificValue<PhysicalQuantity.Speed, SpeedUnit>.times(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = weight * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.speed

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.force.times
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.ImperialForce
import com.splendo.kaluga.scientific.unit.ImperialSpeed
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.MetricSpeed
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.UKImperialForce
import com.splendo.kaluga.scientific.unit.USCustomaryForce
import kotlin.jvm.JvmName

@JvmName("metricSpeedTimesDyne")
infix operator fun ScientificValue<PhysicalQuantity.Speed, MetricSpeed>.times(force: ScientificValue<PhysicalQuantity.Force, Dyne>) =
    force * this

@JvmName("metricSpeedTimesDyneMultiple")
infix operator fun <DyneUnit> ScientificValue<PhysicalQuantity.Speed, MetricSpeed>.times(force: ScientificValue<PhysicalQuantity.Force, DyneUnit>) where DyneUnit : Force, DyneUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> =
    force * this

@JvmName("imperialSpeedTimesImperialForce")
infix operator fun <ForceUnit : ImperialForce> ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>.times(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = force * this

@JvmName("imperialSpeedTimesUKImperialForce")
infix operator fun <ForceUnit : UKImperialForce> ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>.times(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = force * this

@JvmName("imperialSpeedTimesUSCustomaryForce")
infix operator fun <ForceUnit : USCustomaryForce> ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>.times(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = force * this

@JvmName("speedTimesMetricForce")
infix operator fun <ForceUnit : Force, SpeedUnit : Speed> ScientificValue<PhysicalQuantity.Speed, SpeedUnit>.times(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = force * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.speed

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.Speed
import kotlin.jvm.JvmName

@JvmName("speedDivAcceleration")
infix operator fun <SpeedUnit : Speed, AccelerationUnit : Acceleration> ScientificValue<PhysicalQuantity.Speed, SpeedUnit>.div(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>
) = acceleration.unit.per.time(this, acceleration)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.surfaceTension

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.SurfaceTension
import kotlin.jvm.JvmName

@JvmName("surfaceTensionFromEnergyAndAreaDefault")
fun <
    EnergyUnit : Energy,
    AreaUnit : Area,
    SurfaceTensionUnit : SurfaceTension
    > SurfaceTensionUnit.surfaceTension(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = surfaceTension(energy, area, ::DefaultScientificValue)

@JvmName("surfaceTensionFromEnergyAndArea")
fun <
    EnergyUnit : Energy,
    AreaUnit : Area,
    SurfaceTensionUnit : SurfaceTension,
    Value : ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>
    > SurfaceTensionUnit.surfaceTension(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, SurfaceTensionUnit) -> Value
) = byDividing(energy, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.surfaceTension

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.SurfaceTension
import kotlin.jvm.JvmName

@JvmName("surfaceTensionFromForceAndLengthDefault")
fun <
    ForceUnit : Force,
    LengthUnit : Length,
    SurfaceTensionUnit : SurfaceTension
    > SurfaceTensionUnit.surfaceTension(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = surfaceTension(force, length, ::DefaultScientificValue)

@JvmName("surfaceTensionFromForceAndLength")
fun <
    ForceUnit : Force,
    LengthUnit : Length,
    SurfaceTensionUnit : SurfaceTension,
    Value : ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>
    > SurfaceTensionUnit.surfaceTension(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    factory: (Decimal, SurfaceTensionUnit) -> Value
) = byDividing(force, length, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.surfaceTension

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.energy.energy
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Erg
import com.splendo.kaluga.scientific.unit.FootPoundForce
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialSurfaceTension
import com.splendo.kaluga.scientific.unit.InchPoundForce
import com.splendo.kaluga.scientific.unit.Joule
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricSurfaceTension
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.SquareInch
import com.splendo.kaluga.scientific.unit.SurfaceTension
import com.splendo.kaluga.scientific.unit.UKImperialSurfaceTension
import com.splendo.kaluga.scientific.unit.USCustomarySurfaceTension
import kotlin.jvm.JvmName

@JvmName("metricSurfaceTensionTimesSquareCentimeter")
infix operator fun ScientificValue<PhysicalQuantity.SurfaceTension, MetricSurfaceTension>.times(area: ScientificValue<PhysicalQuantity.Area, SquareCentimeter>) =
    Erg.energy(this, area)

@JvmName("metricSurfaceTensionTimesMetricArea")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.SurfaceTension, MetricSurfaceTension>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Joule.energy(this, area)

@JvmName("imperialSurfaceTensionTimesSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.SurfaceTension, ImperialSurfaceTension>.times(
    area: ScientificValue<PhysicalQuantity.Area, SquareInch>
) = InchPoundForce.energy(this, area)

@JvmName("ukImperialSurfaceTensionTimesSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.SurfaceTension, UKImperialSurfaceTension>.times(
    area: ScientificValue<PhysicalQuantity.Area, SquareInch>
) = InchPoundForce.energy(this, area)

@JvmName("usCustomarySurfaceTensionTimesSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.SurfaceTension, USCustomarySurfaceTension>.times(
    area: ScientificValue<PhysicalQuantity.Area, SquareInch>
) = InchPoundForce.energy(this, area)

@JvmName("imperialSurfaceTensionTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.SurfaceTension, ImperialSurfaceTension>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = FootPoundForce.energy(this, area)

@JvmName("ukImperialSurfaceTensionTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.SurfaceTension, UKImperialSurfaceTension>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = FootPoundForce.energy(this, area)

@JvmName("usCustomarySurfaceTensionTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.SurfaceTension, USCustomarySurfaceTension>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = FootPoundForce.energy(this, area)

@JvmName("surfaceTensionTimesArea")
infix operator fun <SurfaceTensionUnit : SurfaceTension, AreaUnit : Area> ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Joule.energy(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.surfaceTension

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.force.force
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.ImperialSurfaceTension
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.MetricSurfaceTension
import com.splendo.kaluga.scientific.unit.Newton
import com.splendo.kaluga.scientific.unit.SurfaceTension
import com.splendo.kaluga.scientific.unit.UKImperialSurfaceTension
import com.splendo.kaluga.scientific.unit.USCustomarySurfaceTension
import kotlin.jvm.JvmName

@JvmName("metricSurfaceTensionTimesMetricLength")
infix operator fun <LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.SurfaceTension, MetricSurfaceTension>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = unit.force.force(this, length)

@JvmName("imperialSurfaceTensionTimesImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.SurfaceTension, ImperialSurfaceTension>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = unit.force.force(this, length)

@JvmName("ukImperialSurfaceTensionTimesImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.SurfaceTension, UKImperialSurfaceTension>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = unit.force.force(this, length)

@JvmName("usCustomarySurfaceTensionTimesImperialLength")
infix operator fun <LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.SurfaceTension, USCustomarySurfaceTension>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = unit.force.force(this, length)

@JvmName("surfaceTensionTimesLength")
infix operator fun <SurfaceTensionUnit : SurfaceTension, LengthUnit : Length> ScientificValue<PhysicalQuantity.SurfaceTension, SurfaceTensionUnit>.times(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = Newton.force(this, length)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.temperature

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.unit.Temperature
import com.splendo.kaluga.scientific.unit.convertDelta

fun <
    TemperatureUnit : Temperature,
    TargetTemperatureUnit : Temperature
    > TargetTemperatureUnit.deltaValue(value: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>) =
    deltaValue(value, ::DefaultScientificValue)

fun <
    TemperatureUnit : Temperature,
    TargetTemperatureUnit : Temperature,
    Value : ScientificValue<PhysicalQuantity.Temperature, TargetTemperatureUnit>
    > TargetTemperatureUnit.deltaValue(
    value: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>,
    factory: (Decimal, TargetTemperatureUnit) -> Value
) = factory(value.unit.convertDelta(value.decimalValue, this), this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.temperature

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.HeatCapacity
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.Temperature
import kotlin.jvm.JvmName

@JvmName("temperatureFromEnergyAndHeatCapacityDefault")
fun <
    EnergyUnit : Energy,
    TemperatureUnit : Temperature,
    HeatCapacityUnit : HeatCapacity
    > TemperatureUnit.temperature(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, HeatCapacityUnit>
) = temperature(energy, heatCapacity, ::DefaultScientificValue)

@JvmName("temperatureFromEnergyAndHeatCapacity")
fun <
    EnergyUnit : Energy,
    TemperatureUnit : Temperature,
    HeatCapacityUnit : HeatCapacity,
    Value : ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
    > TemperatureUnit.temperature(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, HeatCapacityUnit>,
    factory: (Decimal, TemperatureUnit) -> Value
) = deltaValue(Kelvin.byDividing(energy, heatCapacity, ::DefaultScientificValue), factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.temperature

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.SpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.Temperature
import kotlin.jvm.JvmName

@JvmName("temperatureFromSpecificEnergyAndSpecificHeatCapacityDefault")
fun <
    SpecificEnergyUnit : SpecificEnergy,
    TemperatureUnit : Temperature,
    SpecificHeatCapacityUnit : SpecificHeatCapacity
    > TemperatureUnit.temperature(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>
) = temperature(specificEnergy, specificHeatCapacity, ::DefaultScientificValue)

@JvmName("temperatureFromSpecificEnergyAndSpecificHeatCapacity")
fun <
    SpecificEnergyUnit : SpecificEnergy,
    TemperatureUnit : Temperature,
    SpecificHeatCapacityUnit : SpecificHeatCapacity,
    Value : ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
    > TemperatureUnit.temperature(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>,
    factory: (Decimal, TemperatureUnit) -> Value
) = deltaValue(
    Kelvin.byDividing(specificEnergy, specificHeatCapacity, ::DefaultScientificValue),
    factory
)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.temperature

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Temperature
import com.splendo.kaluga.scientific.unit.ThermalResistance
import kotlin.jvm.JvmName

@JvmName("temperatureFromThermalResistanceAndPowerDefault")
fun <
    TemperatureUnit : Temperature,
    PowerUnit : Power,
    ThermalResistanceUnit : ThermalResistance
    > TemperatureUnit.temperature(
    thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, ThermalResistanceUnit>,
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = temperature(thermalResistance, power, ::DefaultScientificValue)

@JvmName("temperatureFromThermalResistanceAndPower")
fun <
    TemperatureUnit : Temperature,
    PowerUnit : Power,
    ThermalResistanceUnit : ThermalResistance,
    Value : ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>
    > TemperatureUnit.temperature(
    thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, ThermalResistanceUnit>,
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>,
    factory: (Decimal, TemperatureUnit) -> Value
) = deltaValue(Kelvin.byMultiplying(thermalResistance, power, ::DefaultScientificValue), factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.temperature

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.heatCapacity.times
import com.splendo.kaluga.scientific.unit.HeatCapacity
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialHeatCapacity
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialTemperature
import com.splendo.kaluga.scientific.unit.MetricHeatCapacity
import com.splendo.kaluga.scientific.unit.Temperature
import com.splendo.kaluga.scientific.unit.UKImperialHeatCapacity
import com.splendo.kaluga.scientific.unit.USCustomaryHeatCapacity
import com.splendo.kaluga.scientific.unit.USCustomaryTemperature
import kotlin.jvm.JvmName

@JvmName("metricAndUKImperialTemperatureTimesMetricAndUKImperialHeatCapacity")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.times(
    heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, MetricAndUKImperialHeatCapacity>
) = heatCapacity * this

@JvmName("metricAndUKImperialTemperatureTimesMetricHeatCapacity")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.times(
    heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, MetricHeatCapacity>
) = heatCapacity * this

@JvmName("metricAndUKImperialTemperatureTimesUKImperialHeatCapacity")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.times(
    heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, UKImperialHeatCapacity>
) = heatCapacity * this

@JvmName("usCustomaryTemperatureTimesUSCustomaryHeatCapacity")
infix operator fun <TemperatureUnit : USCustomaryTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.times(
    heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, USCustomaryHeatCapacity>
) = heatCapacity * this

@JvmName("temperatureTimesHeatCapacity")
infix operator fun <HeatCapacityUnit : HeatCapacity, TemperatureUnit : Temperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.times(
    heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, HeatCapacityUnit>
) = heatCapacity * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.temperature

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.power.power
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialTemperature
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialThermalResistance
import com.splendo.kaluga.scientific.unit.MetricThermalResistance
import com.splendo.kaluga.scientific.unit.Temperature
import com.splendo.kaluga.scientific.unit.ThermalResistance
import com.splendo.kaluga.scientific.unit.UKImperialThermalResistance
import com.splendo.kaluga.scientific.unit.USCustomaryTemperature
import com.splendo.kaluga.scientific.unit.USCustomaryThermalResistance
import com.splendo.kaluga.scientific.unit.Watt
import kotlin.jvm.JvmName

@JvmName("metricAndUKImperialTemperatureDivMetricAndUKImperialThermalResistance")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.div(
    thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, MetricAndUKImperialThermalResistance>
) = thermalResistance.unit.per.power(this, thermalResistance)

@JvmName("metricAndUKImperialTemperatureDivMetricThermalResistance")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.div(
    thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, MetricThermalResistance>
) = thermalResistance.unit.per.power(this, thermalResistance)

@JvmName("metricAndUKImperialTemperatureDivUKImperialThermalResistance")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.div(
    thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, UKImperialThermalResistance>
) = thermalResistance.unit.per.power(this, thermalResistance)

@JvmName("usCustomaryTemperatureDivUSCustomaryThermalResistance")
infix operator fun <TemperatureUnit : USCustomaryTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.div(
    thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, USCustomaryThermalResistance>
) = thermalResistance.unit.per.power(this, thermalResistance)

@JvmName("temperatureDivThermalResistance")
infix operator fun <TemperatureUnit : Temperature, ThermalResistanceUnit : ThermalResistance> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.div(
    thermalResistance: ScientificValue<PhysicalQuantity.ThermalResistance, ThermalResistanceUnit>
) = Watt.power(this, thermalResistance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.temperature

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.specificHeatCapacity.times
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialTemperature
import com.splendo.kaluga.scientific.unit.MetricSpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.SpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.Temperature
import com.splendo.kaluga.scientific.unit.UKImperialSpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.USCustomaryTemperature
import kotlin.jvm.JvmName

@JvmName("metricAndUKImperialTemperatureTimesMetricSpecificHeatCapacity")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.times(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, MetricSpecificHeatCapacity>
) = specificHeatCapacity * this

@JvmName("metricAndUKImperialTemperatureTimesUKImperialSpecificHeatCapacity")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.times(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, UKImperialSpecificHeatCapacity>
) = specificHeatCapacity * this

@JvmName("usCustomaryTemperatureTimesUSCustomarySpecificHeatCapacity")
infix operator fun <TemperatureUnit : USCustomaryTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.times(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, USCustomarySpecificHeatCapacity>
) = specificHeatCapacity * this

@JvmName("temperatureTimesHeatCapacity")
infix operator fun <SpecificHeatCapacityUnit : SpecificHeatCapacity, TemperatureUnit : Temperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.times(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>
) = specificHeatCapacity * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.temperature

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.thermalResistance.thermalResistance
import com.splendo.kaluga.scientific.unit.ImperialPower
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.MetricAndImperialPower
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialTemperature
import com.splendo.kaluga.scientific.unit.MetricPower
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Temperature
import com.splendo.kaluga.scientific.unit.USCustomaryTemperature
import com.splendo.kaluga.scientific.unit.Watt
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricAndUKImperialTemperatureDivMetricAndImperialPower")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.div(
    power: ScientificValue<PhysicalQuantity.Power, MetricAndImperialPower>
) = (unit per power.unit).thermalResistance(this, power)

@JvmName("metricAndUKImperialTemperatureDivMetricPower")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.div(
    power: ScientificValue<PhysicalQuantity.Power, MetricPower>
) = (unit per power.unit).thermalResistance(this, power)

@JvmName("metricAndUKImperialTemperatureDivImperialPower")
infix operator fun <TemperatureUnit : MetricAndUKImperialTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.div(
    power: ScientificValue<PhysicalQuantity.Power, ImperialPower>
) = (unit per power.unit).thermalResistance(this, power)

@JvmName("usCustomaryTemperatureDivMetricAndImperialPower")
infix operator fun <TemperatureUnit : USCustomaryTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.div(
    power: ScientificValue<PhysicalQuantity.Power, MetricAndImperialPower>
) = (unit per power.unit).thermalResistance(this, power)

@JvmName("usCustomaryTemperatureDivImperialPower")
infix operator fun <TemperatureUnit : USCustomaryTemperature> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.div(
    power: ScientificValue<PhysicalQuantity.Power, ImperialPower>
) = (unit per power.unit).thermalResistance(this, power)

@JvmName("temperatureDivPower")
infix operator fun <TemperatureUnit : Temperature, PowerUnit : Power> ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>.div(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = (Kelvin per Watt).thermalResistance(this, power)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.thermalResistance

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.converter.temperature.deltaValue
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Temperature
import com.splendo.kaluga.scientific.unit.ThermalResistance
import kotlin.jvm.JvmName

@JvmName("thermalResistanceFromTemperatureAndPowerDefault")
fun <
    TemperatureUnit : Temperature,
    PowerUnit : Power,
    ThermalResistanceUnit : ThermalResistance
    > ThermalResistanceUnit.thermalResistance(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>,
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = thermalResistance(temperature, power, ::DefaultScientificValue)

@JvmName("thermalResistanceFromTemperatureAndPower")
fun <
    TemperatureUnit : Temperature,
    PowerUnit : Power,
    ThermalResistanceUnit : ThermalResistance,
    Value : ScientificValue<PhysicalQuantity.ThermalResistance, ThermalResistanceUnit>
    > ThermalResistanceUnit.thermalResistance(
    temperature: ScientificValue<PhysicalQuantity.Temperature, TemperatureUnit>,
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>,
    factory: (Decimal, ThermalResistanceUnit) -> Value
) = byDividing(Kelvin.deltaValue(temperature), power, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.thermalResistance

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.temperature.temperature
import com.splendo.kaluga.scientific.unit.ImperialPower
import com.splendo.kaluga.scientific.unit.Kelvin
import com.splendo.kaluga.scientific.unit.MetricAndImperialPower
import com.splendo.kaluga.scientific.unit.MetricAndUKImperialThermalResistance
import com.splendo.kaluga.scientific.unit.MetricPower
import com.splendo.kaluga.scientific.unit.MetricThermalResistance
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.ThermalResistance
import com.splendo.kaluga.scientific.unit.UKImperialThermalResistance
import com.splendo.kaluga.scientific.unit.USCustomaryThermalResistance
import kotlin.jvm.JvmName

@JvmName("metricAndUKImperialThermalResistanceTimesPower")
infix operator fun <PowerUnit : Power> ScientificValue<PhysicalQuantity.ThermalResistance, MetricAndUKImperialThermalResistance>.times(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = unit.temperature.temperature(this, power)

@JvmName("metricThermalResistanceTimesMetricAndImperialPower")
infix operator fun <PowerUnit : MetricAndImperialPower> ScientificValue<PhysicalQuantity.ThermalResistance, MetricThermalResistance>.times(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = unit.temperature.temperature(this, power)

@JvmName("metricThermalResistanceTimesMetricPower")
infix operator fun <PowerUnit : MetricPower> ScientificValue<PhysicalQuantity.ThermalResistance, MetricThermalResistance>.times(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = unit.temperature.temperature(this, power)

@JvmName("ukImperialThermalResistanceTimesMetricAndImperialPower")
infix operator fun <PowerUnit : MetricAndImperialPower> ScientificValue<PhysicalQuantity.ThermalResistance, UKImperialThermalResistance>.times(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = unit.temperature.temperature(this, power)

@JvmName("ukImperialThermalResistanceTimesImperialPower")
infix operator fun <PowerUnit : ImperialPower> ScientificValue<PhysicalQuantity.ThermalResistance, UKImperialThermalResistance>.times(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = unit.temperature.temperature(this, power)

@JvmName("usCustomaryThermalResistanceTimesMetricAndImperialPower")
infix operator fun <PowerUnit : MetricAndImperialPower> ScientificValue<PhysicalQuantity.ThermalResistance, USCustomaryThermalResistance>.times(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = unit.temperature.temperature(this, power)

@JvmName("usCustomaryThermalResistanceTimesImperialPower")
infix operator fun <PowerUnit : ImperialPower> ScientificValue<PhysicalQuantity.ThermalResistance, USCustomaryThermalResistance>.times(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = unit.temperature.temperature(this, power)

@JvmName("thermalResistanceTimesPower")
infix operator fun <ThermalResistanceUnit : ThermalResistance, PowerUnit : Power> ScientificValue<PhysicalQuantity.ThermalResistance, ThermalResistanceUnit>.times(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = Kelvin.temperature(this, power)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.Jolt
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromAccelerationAndJoltDefault")
fun <
    AccelerationUnit : Acceleration,
    TimeUnit : Time,
    JoltUnit : Jolt
    > TimeUnit.time(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>,
    jolt: ScientificValue<PhysicalQuantity.Jolt, JoltUnit>
) = time(acceleration, jolt, ::DefaultScientificValue)

@JvmName("timeFromAccelerationAndJolt")
fun <
    AccelerationUnit : Acceleration,
    TimeUnit : Time,
    JoltUnit : Jolt,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>,
    jolt: ScientificValue<PhysicalQuantity.Jolt, JoltUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(acceleration, jolt, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Action
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromActionAndEnergyDefault")
fun <
    ActionUnit : Action,
    TimeUnit : Time,
    EnergyUnit : Energy
    > TimeUnit.time(
    action: ScientificValue<PhysicalQuantity.Action, ActionUnit>,
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
) = time(action, energy, ::DefaultScientificValue)

@JvmName("timeFromActionAndEnergy")
fun <
    ActionUnit : Action,
    TimeUnit : Time,
    EnergyUnit : Energy,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    action: ScientificValue<PhysicalQuantity.Action, ActionUnit>,
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(action, energy, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.CatalysticActivity
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromAmountOfSubstanceAndCatalysticActivityDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    TimeUnit : Time,
    CatalysisUnit : CatalysticActivity
    > TimeUnit.time(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    catalysis: ScientificValue<PhysicalQuantity.CatalysticActivity, CatalysisUnit>
) = time(amountOfSubstance, catalysis, ::DefaultScientificValue)

@JvmName("timeFromAmountOfSubstanceAndCatalysticActivity")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    TimeUnit : Time,
    CatalysisUnit : CatalysticActivity,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    catalysis: ScientificValue<PhysicalQuantity.CatalysticActivity, CatalysisUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(amountOfSubstance, catalysis, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Angle
import com.splendo.kaluga.scientific.unit.AngularVelocity
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromAngleAndAngularVelocityDefault")
fun <
    AngleUnit : Angle,
    TimeUnit : Time
    > TimeUnit.time(
    angle: ScientificValue<PhysicalQuantity.Angle, AngleUnit>,
    velocity: ScientificValue<PhysicalQuantity.AngularVelocity, AngularVelocity>
) = time(angle, velocity, ::DefaultScientificValue)

@JvmName("timeFromAngleAndAngularVelocity")
fun <
    AngleUnit : Angle,
    TimeUnit : Time,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    angle: ScientificValue<PhysicalQuantity.Angle, AngleUnit>,
    velocity: ScientificValue<PhysicalQuantity.AngularVelocity, AngularVelocity>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(angle, velocity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AngularAcceleration
import com.splendo.kaluga.scientific.unit.AngularVelocity
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromAngularAccelerationAndVelocityDefault")
fun <
    TimeUnit : Time
    > TimeUnit.time(
    velocity: ScientificValue<PhysicalQuantity.AngularVelocity, AngularVelocity>,
    acceleration: ScientificValue<PhysicalQuantity.AngularAcceleration, AngularAcceleration>
) = time(velocity, acceleration, ::DefaultScientificValue)

@JvmName("timeFromAngularAccelerationAndVelocity")
fun <
    TimeUnit : Time,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    velocity: ScientificValue<PhysicalQuantity.AngularVelocity, AngularVelocity>,
    acceleration: ScientificValue<PhysicalQuantity.AngularAcceleration, AngularAcceleration>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(velocity, acceleration, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromChargeAndCurrentDefault")
fun <
    CurrentUnit : ElectricCurrent,
    TimeUnit : Time,
    ChargeUnit : ElectricCharge
    > TimeUnit.duration(
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
) = duration(charge, current, ::DefaultScientificValue)

@JvmName("timeFromChargeAndCurrent")
fun <
    CurrentUnit : ElectricCurrent,
    TimeUnit : Time,
    ChargeUnit : ElectricCharge,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.duration(
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(charge, current, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.DynamicViscosity
import com.splendo.kaluga.scientific.unit.Pressure
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromDynamicViscosityAndPressureDefault")
fun <
    DynamicViscosityUnit : DynamicViscosity,
    TimeUnit : Time,
    PressureUnit : Pressure
    > TimeUnit.time(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, DynamicViscosityUnit>,
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = time(dynamicViscosity, pressure, ::DefaultScientificValue)

@JvmName("timeFromDynamicViscosityAndPressure")
fun <
    DynamicViscosityUnit : DynamicViscosity,
    TimeUnit : Time,
    PressureUnit : Pressure,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    dynamicViscosity: ScientificValue<PhysicalQuantity.DynamicViscosity, DynamicViscosityUnit>,
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(dynamicViscosity, pressure, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromCapacitanceAndResistanceDefault")
fun <
    CapacitanceUnit : ElectricCapacitance,
    TimeUnit : Time,
    ResistanceUnit : ElectricResistance
    > TimeUnit.duration(
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>,
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
) = duration(capacitance, resistance, ::DefaultScientificValue)

@JvmName("timeFromCapacitanceAndResistance")
fun <
    CapacitanceUnit : ElectricCapacitance,
    TimeUnit : Time,
    ResistanceUnit : ElectricResistance,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.duration(
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>,
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byMultiplying(capacitance, resistance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricInductance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromInductanceAndResistanceDefault")
fun <
    ResistanceUnit : ElectricResistance,
    TimeUnit : Time,
    InductanceUnit : ElectricInductance
    > TimeUnit.time(
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>,
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
) = time(inductance, resistance, ::DefaultScientificValue)

@JvmName("timeFromInductanceAndResistance")
fun <
    ResistanceUnit : ElectricResistance,
    TimeUnit : Time,
    InductanceUnit : ElectricInductance,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    inductance: ScientificValue<PhysicalQuantity.ElectricInductance, InductanceUnit>,
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(inductance, resistance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromEnergyAndPowerDefault")
fun <
    EnergyUnit : Energy,
    TimeUnit : Time,
    PowerUnit : Power
    > TimeUnit.time(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = time(energy, power, ::DefaultScientificValue)

@JvmName("timeFromEnergyAndPower")
fun <
    EnergyUnit : Energy,
    TimeUnit : Time,
    PowerUnit : Power,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(energy, power, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Yank
import kotlin.jvm.JvmName

@JvmName("timeFromForceAndYankDefault")
fun <
    ForceUnit : Force,
    TimeUnit : Time,
    YankUnit : Yank
    > TimeUnit.time(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    yank: ScientificValue<PhysicalQuantity.Yank, YankUnit>
) = time(force, yank, ::DefaultScientificValue)

@JvmName("timeFromForceAndYank")
fun <
    ForceUnit : Force,
    TimeUnit : Time,
    YankUnit : Yank,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    yank: ScientificValue<PhysicalQuantity.Yank, YankUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(force, yank, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byInverting
import com.splendo.kaluga.scientific.convert
import com.splendo.kaluga.scientific.convertValue
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.Frequency
import com.splendo.kaluga.scientific.unit.Hertz
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromInvertedFrequencyDefault")
fun <
    TimeUnit : Time,
    FrequencyUnit : Frequency,
    > TimeUnit.time(
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
) = time(frequency, ::DefaultScientificValue)

@JvmName("timeFromInvertedFrequency")
fun <
    TimeUnit : Time,
    FrequencyUnit : Frequency,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byInverting(frequency, factory)

fun <
    FrequencyUnit : Frequency,
    TimeUnit : Time
    > TimeUnit.time(
    cycle: Decimal,
    at: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
) = time(cycle, at, ::DefaultScientificValue)

fun <
    FrequencyUnit : Frequency,
    TimeUnit : Time,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    cycle: Decimal,
    at: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = (cycle / at.convertValue(Hertz))(Second).convert(this, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromDistanceAndSpeedDefault")
fun <
    LengthUnit : Length,
    TimeUnit : Time,
    SpeedUnit : Speed
    > TimeUnit.time(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>
) = time(distance, speed, ::DefaultScientificValue)

@JvmName("timeFromDistanceAndSpeed")
fun <
    LengthUnit : Length,
    TimeUnit : Time,
    SpeedUnit : Speed,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    distance: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(distance, speed, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.LuminousEnergy
import com.splendo.kaluga.scientific.unit.LuminousFlux
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromLuminousEnergyAndFluxDefault")
fun <
    TimeUnit : Time,
    LuminousFluxUnit : LuminousFlux
    > TimeUnit.time(
    luminousEnergy: ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>,
    luminousFlux: ScientificValue<PhysicalQuantity.LuminousFlux, LuminousFluxUnit>
) = time(luminousEnergy, luminousFlux, ::DefaultScientificValue)

@JvmName("timeFromLuminousEnergyAndFlux")
fun <
    TimeUnit : Time,
    LuminousFluxUnit : LuminousFlux,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    luminousEnergy: ScientificValue<PhysicalQuantity.LuminousEnergy, LuminousEnergy>,
    luminousFlux: ScientificValue<PhysicalQuantity.LuminousFlux, LuminousFluxUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(luminousEnergy, luminousFlux, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.LuminousExposure
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromLuminousExposureAndIlluminanceDefault")
fun <
    LuminousExposureUnit : LuminousExposure,
    TimeUnit : Time,
    IlluminanceUnit : Illuminance
    > TimeUnit.time(
    luminousExposure: ScientificValue<PhysicalQuantity.LuminousExposure, LuminousExposureUnit>,
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>
) = time(luminousExposure, illuminance, ::DefaultScientificValue)

@JvmName("timeFromLuminousExposureAndIlluminance")
fun <
    LuminousExposureUnit : LuminousExposure,
    TimeUnit : Time,
    IlluminanceUnit : Illuminance,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    luminousExposure: ScientificValue<PhysicalQuantity.LuminousExposure, LuminousExposureUnit>,
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(luminousExposure, illuminance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("timeFromFluxAndVoltageDefault")
fun <
    VoltageUnit : Voltage,
    TimeUnit : Time,
    FluxUnit : MagneticFlux
    > TimeUnit.time(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = time(flux, voltage, ::DefaultScientificValue)

@JvmName("timeFromFluxAndVoltage")
fun <
    VoltageUnit : Voltage,
    TimeUnit : Time,
    FluxUnit : MagneticFlux,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(flux, voltage, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.base.utils.div
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byInverting
import com.splendo.kaluga.scientific.convert
import com.splendo.kaluga.scientific.convertValue
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.Becquerel
import com.splendo.kaluga.scientific.unit.Radioactivity
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromInvertedRadioactivityDefault")
fun <
    TimeUnit : Time,
    RadioactivityUnit : Radioactivity
    > TimeUnit.time(
    radioactivity: ScientificValue<PhysicalQuantity.Radioactivity, RadioactivityUnit>
) = time(radioactivity, ::DefaultScientificValue)

@JvmName("timeFromInvertedRadioactivity")
fun <
    TimeUnit : Time,
    RadioactivityUnit : Radioactivity,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    radioactivity: ScientificValue<PhysicalQuantity.Radioactivity, RadioactivityUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byInverting(radioactivity, factory)

fun <
    RadioactivityUnit : Radioactivity,
    TimeUnit : Time
    > TimeUnit.time(
    decay: Decimal,
    at: ScientificValue<PhysicalQuantity.Radioactivity, RadioactivityUnit>
) = time(decay, at, ::DefaultScientificValue)

fun <
    RadioactivityUnit : Radioactivity,
    TimeUnit : Time,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    decay: Decimal,
    at: ScientificValue<PhysicalQuantity.Radioactivity, RadioactivityUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = (decay / at.convertValue(Becquerel))(Second).convert(this, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeFromSpeedAndAccelerationDefault")
fun <
    SpeedUnit : Speed,
    TimeUnit : Time,
    AccelerationUnit : Acceleration
    > TimeUnit.time(
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>,
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>
) = time(speed, acceleration, ::DefaultScientificValue)

@JvmName("timeFromSpeedAndAcceleration")
fun <
    SpeedUnit : Speed,
    TimeUnit : Time,
    AccelerationUnit : Acceleration,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>,
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(speed, acceleration, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.MassFlowRate
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("timeFromWeightAndMassFlowRateDefault")
fun <
    WeightUnit : Weight,
    TimeUnit : Time,
    MassFlowRateUnit : MassFlowRate
    > TimeUnit.time(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, MassFlowRateUnit>
) = time(weight, massFlowRate, ::DefaultScientificValue)

@JvmName("timeFromWeightAndMassFlowRate")
fun <
    WeightUnit : Weight,
    TimeUnit : Time,
    MassFlowRateUnit : MassFlowRate,
    Value : ScientificValue<PhysicalQuantity.Time, TimeUnit>
    > TimeUnit.time(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, MassFlowRateUnit>,
    factory: (Decimal, TimeUnit) -> Value
) = byDividing(weight, massFlowRate, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.jolt.times
import com.splendo.kaluga.scientific.unit.ImperialJolt
import com.splendo.kaluga.scientific.unit.Jolt
import com.splendo.kaluga.scientific.unit.MetricJolt
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeTimesMetricJolt")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(jolt: ScientificValue<PhysicalQuantity.Jolt, MetricJolt>) = jolt * this

@JvmName("timeTimesImperialJolt")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(jolt: ScientificValue<PhysicalQuantity.Jolt, ImperialJolt>) = jolt * this

@JvmName("timeTimesJolt")
infix operator fun <JoltUnit : Jolt, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(jolt: ScientificValue<PhysicalQuantity.Jolt, JoltUnit>) = jolt * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.energy.times
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.ImperialEnergy
import com.splendo.kaluga.scientific.unit.MetricAndImperialEnergy
import com.splendo.kaluga.scientific.unit.MetricEnergy
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeTimesMetricAndImperialEnergy")
infix operator fun <EnergyUnit : MetricAndImperialEnergy, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
) = energy * this

@JvmName("timeTimesMetricEnergy")
infix operator fun <EnergyUnit : MetricEnergy, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
) = energy * this

@JvmName("timeTimesImperialEnergy")
infix operator fun <EnergyUnit : ImperialEnergy, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
) = energy * this

@JvmName("timeTimesEnergy")
infix operator fun <EnergyUnit : Energy, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>
) = energy * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.catalysticActivity.times
import com.splendo.kaluga.scientific.unit.CatalysticActivity
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeTimesCatalysticActivity")
infix operator fun <TimeUnit : Time, CatalysisUnit : CatalysticActivity> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    catalysis: ScientificValue<PhysicalQuantity.CatalysticActivity, CatalysisUnit>
) = catalysis * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.angularVelocity.times
import com.splendo.kaluga.scientific.unit.AngularVelocity
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeTimesAngularVelocity")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    angularVelocity: ScientificValue<PhysicalQuantity.AngularVelocity, AngularVelocity>
) = angularVelocity * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.angularAcceleration.times
import com.splendo.kaluga.scientific.unit.AngularAcceleration
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeTimesAngularAcceleration")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    angularAcceleration: ScientificValue<PhysicalQuantity.AngularAcceleration, AngularAcceleration>
) = angularAcceleration * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.frequency.times
import com.splendo.kaluga.scientific.converter.radioactivity.times
import com.splendo.kaluga.scientific.unit.Frequency
import com.splendo.kaluga.scientific.unit.Radioactivity
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeTimesFrequency")
infix operator fun <FrequencyUnit : Frequency, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    frequency: ScientificValue<PhysicalQuantity.Frequency, FrequencyUnit>
): Decimal = frequency * this

@JvmName("timeTimesRadioactivity")
infix operator fun <RadioactivityUnit : Radioactivity, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    radioactivity: ScientificValue<PhysicalQuantity.Radioactivity, RadioactivityUnit>
): Decimal = radioactivity * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.pressure.times
import com.splendo.kaluga.scientific.unit.ImperialPressure
import com.splendo.kaluga.scientific.unit.MetricPressure
import com.splendo.kaluga.scientific.unit.Pressure
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.UKImperialPressure
import com.splendo.kaluga.scientific.unit.USCustomaryPressure
import kotlin.jvm.JvmName

@JvmName("timeTimesMetricPressure")
infix operator fun <PressureUnit : MetricPressure, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = pressure * this

@JvmName("timeTimesImperialPressure")
infix operator fun <PressureUnit : ImperialPressure, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = pressure * this

@JvmName("timeTimesUKImperialPressure")
infix operator fun <PressureUnit : UKImperialPressure, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = pressure * this

@JvmName("timeTimesUSCustomaryPressure")
infix operator fun <PressureUnit : USCustomaryPressure, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = pressure * this

@JvmName("timeTimesPressure")
infix operator fun <PressureUnit : Pressure, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = pressure * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCapacitance.capacitance
import com.splendo.kaluga.scientific.unit.Abfarad
import com.splendo.kaluga.scientific.unit.Abohm
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Farad
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeDivAbohm")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.div(resistance: ScientificValue<PhysicalQuantity.ElectricResistance, Abohm>) =
    Abfarad.capacitance(this, resistance)

@JvmName("timeDivResistance")
infix operator fun <TimeUnit : Time, ResistanceUnit : ElectricResistance> ScientificValue<PhysicalQuantity.Time, TimeUnit>.div(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
) = Farad.capacitance(this, resistance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCurrent.times
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Biot
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeTimesAbampere")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>) =
    current * this

@JvmName("timeTimesBiot")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>) =
    current * this

@JvmName("timeTimesCurrent")
infix operator fun <CurrentUnit : ElectricCurrent, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
) = current * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricResistance.times
import com.splendo.kaluga.scientific.unit.Abohm
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeTimesAbohm")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, Abohm>
) = resistance * this

@JvmName("timeTimesResistance")
infix operator fun <ResistanceUnit : ElectricResistance, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
) = resistance * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricResistance.resistance
import com.splendo.kaluga.scientific.unit.Abfarad
import com.splendo.kaluga.scientific.unit.Abohm
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.Ohm
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeDivAbfarad")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.div(capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, Abfarad>) =
    Abohm.resistance(this, capacitance)

@JvmName("timeDivCapacitance")
infix operator fun <TimeUnit : Time, CapacitanceUnit : ElectricCapacitance> ScientificValue<PhysicalQuantity.Time, TimeUnit>.div(
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>
) = Ohm.resistance(this, capacitance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.power.times
import com.splendo.kaluga.scientific.unit.BritishThermalUnitPerHour
import com.splendo.kaluga.scientific.unit.BritishThermalUnitPerMinute
import com.splendo.kaluga.scientific.unit.BritishThermalUnitPerSecond
import com.splendo.kaluga.scientific.unit.Centiwatt
import com.splendo.kaluga.scientific.unit.Decawatt
import com.splendo.kaluga.scientific.unit.Deciwatt
import com.splendo.kaluga.scientific.unit.ErgPerSecond
import com.splendo.kaluga.scientific.unit.Gigawatt
import com.splendo.kaluga.scientific.unit.Hectowatt
import com.splendo.kaluga.scientific.unit.Horsepower
import com.splendo.kaluga.scientific.unit.Hour
import com.splendo.kaluga.scientific.unit.ImperialPower
import com.splendo.kaluga.scientific.unit.InchPoundForcePerMinute
import com.splendo.kaluga.scientific.unit.InchPoundForcePerSecond
import com.splendo.kaluga.scientific.unit.Kilowatt
import com.splendo.kaluga.scientific.unit.Megawatt
import com.splendo.kaluga.scientific.unit.MetricPower
import com.splendo.kaluga.scientific.unit.Microwatt
import com.splendo.kaluga.scientific.unit.Milliwatt
import com.splendo.kaluga.scientific.unit.Nanowatt
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Watt
import kotlin.jvm.JvmName

@JvmName("hourTimesWatt")
infix operator fun ScientificValue<PhysicalQuantity.Time, Hour>.times(power: ScientificValue<PhysicalQuantity.Power, Watt>) =
    power * this

@JvmName("hourTimesNanowatt")
infix operator fun ScientificValue<PhysicalQuantity.Time, Hour>.times(power: ScientificValue<PhysicalQuantity.Power, Nanowatt>) =
    power * this

@JvmName("hourTimesMicrowatt")
infix operator fun ScientificValue<PhysicalQuantity.Time, Hour>.times(power: ScientificValue<PhysicalQuantity.Power, Microwatt>) =
    power * this

@JvmName("hourTimesMilliwatt")
infix operator fun ScientificValue<PhysicalQuantity.Time, Hour>.times(power: ScientificValue<PhysicalQuantity.Power, Milliwatt>) =
    power * this

@JvmName("hourTimesCentiwatt")
infix operator fun ScientificValue<PhysicalQuantity.Time, Hour>.times(power: ScientificValue<PhysicalQuantity.Power, Centiwatt>) =
    power * this

@JvmName("hourTimesDeciwatt")
infix operator fun ScientificValue<PhysicalQuantity.Time, Hour>.times(power: ScientificValue<PhysicalQuantity.Power, Deciwatt>) =
    power * this

@JvmName("hourTimesDecawatt")
infix operator fun ScientificValue<PhysicalQuantity.Time, Hour>.times(power: ScientificValue<PhysicalQuantity.Power, Decawatt>) =
    power * this

@JvmName("hourTimesHectowatt")
infix operator fun ScientificValue<PhysicalQuantity.Time, Hour>.times(power: ScientificValue<PhysicalQuantity.Power, Hectowatt>) =
    power * this

@JvmName("hourTimesKilowatt")
infix operator fun ScientificValue<PhysicalQuantity.Time, Hour>.times(power: ScientificValue<PhysicalQuantity.Power, Kilowatt>) =
    power * this

@JvmName("hourTimesMegawatt")
infix operator fun ScientificValue<PhysicalQuantity.Time, Hour>.times(power: ScientificValue<PhysicalQuantity.Power, Megawatt>) =
    power * this

@JvmName("hourTimesGigawatt")
infix operator fun ScientificValue<PhysicalQuantity.Time, Hour>.times(power: ScientificValue<PhysicalQuantity.Power, Gigawatt>) =
    power * this

@JvmName("timeTimesErgPerSecond")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(power: ScientificValue<PhysicalQuantity.Power, ErgPerSecond>) =
    power * this

@JvmName("timeTimesMetricPower")
infix operator fun <PowerUnit : MetricPower, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = power * this

@JvmName("timeTimesInchPoundForcePerSecond")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    power: ScientificValue<PhysicalQuantity.Power, InchPoundForcePerSecond>
) = power * this

@JvmName("timeTimesInchPoundForcePerMinute")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    power: ScientificValue<PhysicalQuantity.Power, InchPoundForcePerMinute>
) = power * this

@JvmName("hourTimesHorsepower")
infix operator fun ScientificValue<PhysicalQuantity.Time, Hour>.times(power: ScientificValue<PhysicalQuantity.Power, Horsepower>) =
    power * this

@JvmName("timeTimesBritishThermalUnitPerSecond")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(power: ScientificValue<PhysicalQuantity.Power, BritishThermalUnitPerSecond>) =
    power * this

@JvmName("timeTimesBritishThermalUnitPerMinute")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(power: ScientificValue<PhysicalQuantity.Power, BritishThermalUnitPerMinute>) =
    power * this

@JvmName("timeTimesBritishThermalUnitPerHour")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(power: ScientificValue<PhysicalQuantity.Power, BritishThermalUnitPerHour>) =
    power * this

@JvmName("timeTimesImperialPower")
infix operator fun <PowerUnit : ImperialPower, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = power * this

@JvmName("timeTimesPower")
infix operator fun <PowerUnit : Power, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>
) = power * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.yank.times
import com.splendo.kaluga.scientific.unit.ImperialYank
import com.splendo.kaluga.scientific.unit.MetricYank
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.UKImperialYank
import com.splendo.kaluga.scientific.unit.USCustomaryYank
import com.splendo.kaluga.scientific.unit.Yank
import kotlin.jvm.JvmName

@JvmName("timeTimesMetricYank")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(yank: ScientificValue<PhysicalQuantity.Yank, MetricYank>) =
    yank * this

@JvmName("timeTimesImperialYank")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(yank: ScientificValue<PhysicalQuantity.Yank, ImperialYank>) =
    yank * this

@JvmName("timeTimesUKImperialYank")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(yank: ScientificValue<PhysicalQuantity.Yank, UKImperialYank>) =
    yank * this

@JvmName("timeTimesUSCustomaryYank")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(yank: ScientificValue<PhysicalQuantity.Yank, USCustomaryYank>) =
    yank * this

@JvmName("timeTimesYank")
infix operator fun <YankUnit : Yank, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    yank: ScientificValue<PhysicalQuantity.Yank, YankUnit>
) = yank * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.frequency.frequency
import com.splendo.kaluga.scientific.unit.BeatsPerMinute
import com.splendo.kaluga.scientific.unit.Hertz
import com.splendo.kaluga.scientific.unit.Minute
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("minuteFrequency")
fun ScientificValue<PhysicalQuantity.Time, Minute>.frequency() = BeatsPerMinute.frequency(this)

@JvmName("frequency")
fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.frequency() =
    Hertz.frequency(this)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.speed.times
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.ImperialSpeed
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.MetricSpeed
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeTimesMetricSpeed")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(speed: ScientificValue<PhysicalQuantity.Speed, MetricSpeed>): ScientificValue<PhysicalQuantity.Length, MetricLength> =
    speed * this

@JvmName("timeTimesImperialSpeed")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(speed: ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>): ScientificValue<PhysicalQuantity.Length, ImperialLength> =
    speed * this

@JvmName("timeTimesSpeed")
infix operator fun <SpeedUnit : Speed, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>
): ScientificValue<PhysicalQuantity.Length, Length> = speed * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.luminousFlux.times
import com.splendo.kaluga.scientific.unit.LuminousFlux
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeTimesLuminousFlux")
infix operator fun <LuminousFluxUnit : LuminousFlux, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    luminousFlux: ScientificValue<PhysicalQuantity.LuminousFlux, LuminousFluxUnit>
) = luminousFlux * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.illuminance.times
import com.splendo.kaluga.scientific.unit.Illuminance
import com.splendo.kaluga.scientific.unit.ImperialIlluminance
import com.splendo.kaluga.scientific.unit.MetricIlluminance
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeTimesMetricIlluminance")
infix operator fun <IlluminanceUnit : MetricIlluminance, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>
) = illuminance * this

@JvmName("timeTimesImperialIlluminance")
infix operator fun <IlluminanceUnit : ImperialIlluminance, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>
) = illuminance * this

@JvmName("timeTimesIlluminance")
infix operator fun <IlluminanceUnit : Illuminance, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    illuminance: ScientificValue<PhysicalQuantity.Illuminance, IlluminanceUnit>
) = illuminance * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.voltage.times
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("timeTimesAbvolt")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(voltage: ScientificValue<PhysicalQuantity.Voltage, Abvolt>) =
    voltage * this

@JvmName("timeTimesVoltage")
infix operator fun <VoltageUnit : Voltage, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    voltage: ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
) = voltage * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.force.times
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.GrainForce
import com.splendo.kaluga.scientific.unit.GramForce
import com.splendo.kaluga.scientific.unit.ImperialForce
import com.splendo.kaluga.scientific.unit.ImperialTonForce
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricForce
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.MilligramForce
import com.splendo.kaluga.scientific.unit.OunceForce
import com.splendo.kaluga.scientific.unit.Poundal
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.TonneForce
import com.splendo.kaluga.scientific.unit.UKImperialForce
import com.splendo.kaluga.scientific.unit.USCustomaryForce
import com.splendo.kaluga.scientific.unit.UsTonForce
import kotlin.jvm.JvmName

@JvmName("timeTimesDyne")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(dyne: ScientificValue<PhysicalQuantity.Force, Dyne>) =
    dyne * this

@JvmName("timeTimesDyneMultiple")
infix operator fun <DyneUnit, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    dyne: ScientificValue<PhysicalQuantity.Force, DyneUnit>
) where DyneUnit : MetricForce, DyneUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Force, Dyne> =
    dyne * this

@JvmName("timeTimesTonneForce")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(tonne: ScientificValue<PhysicalQuantity.Force, TonneForce>) =
    tonne * this

@JvmName("timeTimesGramForce")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(gram: ScientificValue<PhysicalQuantity.Force, GramForce>) =
    gram * this

@JvmName("timeTimesMilligramForce")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(milligram: ScientificValue<PhysicalQuantity.Force, MilligramForce>) =
    milligram * this

@JvmName("timeTimesMetricForce")
infix operator fun <ForceUnit : MetricForce, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = force * this

@JvmName("timeTimesPoundal")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(poundal: ScientificValue<PhysicalQuantity.Force, Poundal>) =
    poundal * this

@JvmName("timeTimesOunceForce")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    ounceForce: ScientificValue<PhysicalQuantity.Force, OunceForce>
) = ounceForce * this

@JvmName("timeTimesGrainForce")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    grainForce: ScientificValue<PhysicalQuantity.Force, GrainForce>
) = grainForce * this

@JvmName("timeTimesUsTonForce")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    usTonForce: ScientificValue<PhysicalQuantity.Force, UsTonForce>
) = usTonForce * this

@JvmName("timeTimesImperialTonForce")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    imperialTonForce: ScientificValue<PhysicalQuantity.Force, ImperialTonForce>
) = imperialTonForce * this

@JvmName("timeTimesImperialForce")
infix operator fun <ForceUnit : ImperialForce, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = force * this

@JvmName("timeTimesUKImperialForce")
infix operator fun <ForceUnit : UKImperialForce, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = force * this

@JvmName("timeTimesUSCustomaryForce")
infix operator fun <ForceUnit : USCustomaryForce, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = force * this

@JvmName("timeTimesForce")
infix operator fun <ForceUnit : Force, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>
) = force * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.acceleration.times
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.ImperialAcceleration
import com.splendo.kaluga.scientific.unit.MetricAcceleration
import com.splendo.kaluga.scientific.unit.Time
import kotlin.jvm.JvmName

@JvmName("timeTimesMetricAcceleration")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, MetricAcceleration>
) = acceleration * this

@JvmName("timeTimesImperialAcceleration")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>
) = acceleration * this

@JvmName("timeTimesAcceleration")
infix operator fun <AccelerationUnit : Acceleration, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>
) = acceleration * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.volumetricFlow.times
import com.splendo.kaluga.scientific.unit.ImperialVolumetricFlow
import com.splendo.kaluga.scientific.unit.MetricVolumetricFlow
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.UKImperialVolumetricFlow
import com.splendo.kaluga.scientific.unit.USCustomaryVolumetricFlow
import com.splendo.kaluga.scientific.unit.VolumetricFlow
import kotlin.jvm.JvmName

@JvmName("timeTimesMetricVolumetricFlow")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    volumetricFlow: ScientificValue<PhysicalQuantity.VolumetricFlow, MetricVolumetricFlow>
) = volumetricFlow * this

@JvmName("timeTimesImperialVolumetricFlow")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    volumetricFlow: ScientificValue<PhysicalQuantity.VolumetricFlow, ImperialVolumetricFlow>
) = volumetricFlow * this

@JvmName("timeTimesUKImperialVolumetricFlow")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    volumetricFlow: ScientificValue<PhysicalQuantity.VolumetricFlow, UKImperialVolumetricFlow>
) = volumetricFlow * this

@JvmName("timeTimesUSCustomaryVolumetricFlow")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    volumetricFlow: ScientificValue<PhysicalQuantity.VolumetricFlow, USCustomaryVolumetricFlow>
) = volumetricFlow * this

@JvmName("timeTimesVolumetricFlow")
infix operator fun <VolumetricFlowUnit : VolumetricFlow, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    volumetricFlow: ScientificValue<PhysicalQuantity.VolumetricFlow, VolumetricFlowUnit>
) = volumetricFlow * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.time

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.massFlowRate.times
import com.splendo.kaluga.scientific.unit.ImperialMassFlowRate
import com.splendo.kaluga.scientific.unit.MassFlowRate
import com.splendo.kaluga.scientific.unit.MetricMassFlowRate
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.UKImperialMassFlowRate
import com.splendo.kaluga.scientific.unit.USCustomaryMassFlowRate
import kotlin.jvm.JvmName

@JvmName("timeTimesMetricMassFlowRate")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, MetricMassFlowRate>
) = massFlowRate * this

@JvmName("timeTimesImperialMassFlowRate")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, ImperialMassFlowRate>
) = massFlowRate * this

@JvmName("timeUKImperialMassFlowRate")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, UKImperialMassFlowRate>
) = massFlowRate * this

@JvmName("timeTimesUSCustomaryMassFlowRate")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, USCustomaryMassFlowRate>
) = massFlowRate * this

@JvmName("timeTimesMassFlowRate")
infix operator fun <MassFlowRateUnit : MassFlowRate, TimeUnit : Time> ScientificValue<PhysicalQuantity.Time, TimeUnit>.times(
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, MassFlowRateUnit>
) = massFlowRate * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.voltage

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("voltageFromChargeAndCapacitanceDefault")
fun <
    ChargeUnit : ElectricCharge,
    VoltageUnit : Voltage,
    CapacitanceUnit : ElectricCapacitance
    > VoltageUnit.voltage(
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>,
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>
) = voltage(charge, capacitance, ::DefaultScientificValue)

@JvmName("voltageFromChargeAndCapacitance")
fun <
    ChargeUnit : ElectricCharge,
    VoltageUnit : Voltage,
    CapacitanceUnit : ElectricCapacitance,
    Value : ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
    > VoltageUnit.voltage(
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>,
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>,
    factory: (Decimal, VoltageUnit) -> Value
) = byDividing(charge, capacitance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.voltage

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricConductance
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("voltageFromCurrentAndConductanceDefault")
fun <
    CurrentUnit : ElectricCurrent,
    VoltageUnit : Voltage,
    ConductanceUnit : ElectricConductance
    > VoltageUnit.voltage(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>,
    conductance: ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>
) = voltage(current, conductance, ::DefaultScientificValue)

@JvmName("voltageFromCurrentAndConductance")
fun <
    CurrentUnit : ElectricCurrent,
    VoltageUnit : Voltage,
    ConductanceUnit : ElectricConductance,
    Value : ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
    > VoltageUnit.voltage(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>,
    conductance: ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>,
    factory: (Decimal, VoltageUnit) -> Value
) = byDividing(current, conductance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.voltage

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("voltageFromCurrentAndResistanceDefault")
fun <
    CurrentUnit : ElectricCurrent,
    VoltageUnit : Voltage,
    ResistanceUnit : ElectricResistance
    > VoltageUnit.voltage(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>,
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
) = voltage(current, resistance, ::DefaultScientificValue)

@JvmName("voltageFromCurrentAndResistance")
fun <
    CurrentUnit : ElectricCurrent,
    VoltageUnit : Voltage,
    ResistanceUnit : ElectricResistance,
    Value : ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
    > VoltageUnit.voltage(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>,
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>,
    factory: (Decimal, VoltageUnit) -> Value
) = byMultiplying(current, resistance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.voltage

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("voltageFromEnergyAndChargeDefault")
fun <
    VoltageUnit : Voltage,
    EnergyUnit : Energy,
    ChargeUnit : ElectricCharge
    > VoltageUnit.voltage(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>
) = voltage(energy, charge, ::DefaultScientificValue)

@JvmName("voltageFromEnergyAndCharge")
fun <
    VoltageUnit : Voltage,
    EnergyUnit : Energy,
    ChargeUnit : ElectricCharge,
    Value : ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
    > VoltageUnit.voltage(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>,
    factory: (Decimal, VoltageUnit) -> Value
) = byDividing(energy, charge, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.voltage

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.MagneticFlux
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("voltageFromFluxAndTimeDefault")
fun <
    VoltageUnit : Voltage,
    TimeUnit : Time,
    FluxUnit : MagneticFlux
    > VoltageUnit.voltage(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = voltage(flux, time, ::DefaultScientificValue)

@JvmName("voltageFromFluxAndTime")
fun <
    VoltageUnit : Voltage,
    TimeUnit : Time,
    FluxUnit : MagneticFlux,
    Value : ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
    > VoltageUnit.voltage(
    flux: ScientificValue<PhysicalQuantity.MagneticFlux, FluxUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, VoltageUnit) -> Value
) = byDividing(flux, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.voltage

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Power
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("voltageFromPowerAndCurrentDefault")
fun <
    VoltageUnit : Voltage,
    ElectricCurrentUnit : ElectricCurrent,
    PowerUnit : Power
    > VoltageUnit.voltage(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, ElectricCurrentUnit>
) = voltage(power, current, ::DefaultScientificValue)

@JvmName("voltageFromPowerAndCurrent")
fun <
    VoltageUnit : Voltage,
    ElectricCurrentUnit : ElectricCurrent,
    PowerUnit : Power,
    Value : ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>
    > VoltageUnit.voltage(
    power: ScientificValue<PhysicalQuantity.Power, PowerUnit>,
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, ElectricCurrentUnit>,
    factory: (Decimal, VoltageUnit) -> Value
) = byDividing(power, current, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.voltage

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCapacitance.times
import com.splendo.kaluga.scientific.unit.Abfarad
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.ElectricCapacitance
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("abvoltTimesAbfarad")
infix operator fun ScientificValue<PhysicalQuantity.Voltage, Abvolt>.times(capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, Abfarad>) =
    capacitance * this

@JvmName("voltageTimesCapacitance")
infix operator fun <CapacitanceUnit : ElectricCapacitance, VoltageUnit : Voltage> ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>.times(
    capacitance: ScientificValue<PhysicalQuantity.ElectricCapacitance, CapacitanceUnit>
) = capacitance * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.voltage

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricConductance.times
import com.splendo.kaluga.scientific.converter.electricCurrent.current
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Abohm
import com.splendo.kaluga.scientific.unit.Absiemens
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.Ampere
import com.splendo.kaluga.scientific.unit.ElectricConductance
import com.splendo.kaluga.scientific.unit.ElectricResistance
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("abvoltTimesAbsiemens")
infix operator fun ScientificValue<PhysicalQuantity.Voltage, Abvolt>.times(conductance: ScientificValue<PhysicalQuantity.ElectricConductance, Absiemens>) =
    conductance * this

@JvmName("voltageTimesConductance")
infix operator fun <ConductanceUnit : ElectricConductance, VoltageUnit : Voltage> ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>.times(
    conductance: ScientificValue<PhysicalQuantity.ElectricConductance, ConductanceUnit>
) = conductance * this

@JvmName("abvoltDivAbohm")
infix operator fun ScientificValue<PhysicalQuantity.Voltage, Abvolt>.div(resistance: ScientificValue<PhysicalQuantity.ElectricResistance, Abohm>) =
    Abampere.current(this, resistance)

@JvmName("voltageDivResistance")
infix operator fun <VoltageUnit : Voltage, ResistanceUnit : ElectricResistance> ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>.div(
    resistance: ScientificValue<PhysicalQuantity.ElectricResistance, ResistanceUnit>
) = Ampere.current(this, resistance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.voltage

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricResistance.resistance
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Abohm
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.Biot
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.Ohm
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("abvoltDivAbampere")
infix operator fun ScientificValue<PhysicalQuantity.Voltage, Abvolt>.div(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>) =
    Abohm.resistance(this, current)

@JvmName("abvoltDivBiot")
infix operator fun ScientificValue<PhysicalQuantity.Voltage, Abvolt>.div(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>) =
    Abohm.resistance(this, current)

@JvmName("voltageDivCurrent")
infix operator fun <CurrentUnit : ElectricCurrent, VoltageUnit : Voltage> ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>.div(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, CurrentUnit>
) = Ohm.resistance(this, current)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.voltage

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.electricCharge.times
import com.splendo.kaluga.scientific.unit.Abcoulomb
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.ElectricCharge
import com.splendo.kaluga.scientific.unit.Voltage
import kotlin.jvm.JvmName

@JvmName("abvoltTimesAbcoulomb")
infix operator fun ScientificValue<PhysicalQuantity.Voltage, Abvolt>.times(charge: ScientificValue<PhysicalQuantity.ElectricCharge, Abcoulomb>) =
    charge * this

@JvmName("voltageTimesCharge")
infix operator fun <ChargeUnit : ElectricCharge, VoltageUnit : Voltage> ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>.times(
    charge: ScientificValue<PhysicalQuantity.ElectricCharge, ChargeUnit>
) = charge * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.voltage

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.magneticFlux.flux
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.Maxwell
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Voltage
import com.splendo.kaluga.scientific.unit.Weber
import kotlin.jvm.JvmName

@JvmName("abvoltTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Voltage, Abvolt>.times(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    Maxwell.flux(this, time)

@JvmName("voltageTimesTime")
infix operator fun <VoltageUnit : Voltage, TimeUnit : Time> ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = Weber.flux(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.voltage

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.power.power
import com.splendo.kaluga.scientific.unit.Abampere
import com.splendo.kaluga.scientific.unit.Abvolt
import com.splendo.kaluga.scientific.unit.Biot
import com.splendo.kaluga.scientific.unit.ElectricCurrent
import com.splendo.kaluga.scientific.unit.ErgPerSecond
import com.splendo.kaluga.scientific.unit.Voltage
import com.splendo.kaluga.scientific.unit.Watt
import kotlin.jvm.JvmName

@JvmName("abvoltTimesAbampere")
infix operator fun ScientificValue<PhysicalQuantity.Voltage, Abvolt>.times(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Abampere>) =
    ErgPerSecond.power(this, current)

@JvmName("abvoltTimesBiot")
infix operator fun ScientificValue<PhysicalQuantity.Voltage, Abvolt>.times(current: ScientificValue<PhysicalQuantity.ElectricCurrent, Biot>) =
    ErgPerSecond.power(this, current)

@JvmName("voltageTimesCurrent")
infix operator fun <VoltageUnit : Voltage, ElectricCurrentUnit : ElectricCurrent> ScientificValue<PhysicalQuantity.Voltage, VoltageUnit>.times(
    current: ScientificValue<PhysicalQuantity.ElectricCurrent, ElectricCurrentUnit>
) = Watt.power(this, current)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("volumeFromAmountOfSubstanceAndMolarityDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    VolumeUnit : Volume,
    MolarityUnit : Molarity
    > VolumeUnit.volume(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = volume(amountOfSubstance, molarity, ::DefaultScientificValue)

@JvmName("volumeFromAmountOfSubstanceAndMolarity")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    VolumeUnit : Volume,
    MolarityUnit : Molarity,
    Value : ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
    > VolumeUnit.volume(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>,
    factory: (Decimal, VolumeUnit) -> Value
) = byDividing(amountOfSubstance, molarity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.converter.area.area
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("volumeFromAreaAndHeightDefault")
fun <
    HeightUnit : Length,
    AreaUnit : Area,
    VolumeUnit : Volume
    > VolumeUnit.volume(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    height: ScientificValue<PhysicalQuantity.Length, HeightUnit>
) = volume(area, height, ::DefaultScientificValue)

@JvmName("volumeFromAreaAndHeight")
fun <
    HeightUnit : Length,
    AreaUnit : Area,
    VolumeUnit : Volume,
    Value : ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
    > VolumeUnit.volume(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    height: ScientificValue<PhysicalQuantity.Length, HeightUnit>,
    factory: (Decimal, VolumeUnit) -> Value
) = byMultiplying(area, height, factory)

@JvmName("volumeFromLengthWidthAndHeightDefault")
fun <
    LengthUnit : Length,
    WidthUnit : Length,
    HeightUnit : Length,
    VolumeUnit : Volume
    > VolumeUnit.volume(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    width: ScientificValue<PhysicalQuantity.Length, WidthUnit>,
    height: ScientificValue<PhysicalQuantity.Length, HeightUnit>
) = volume(length, width, height, ::DefaultScientificValue)

@JvmName("volumeFromLengthWidthAndHeight")
fun <
    LengthUnit : Length,
    WidthUnit : Length,
    HeightUnit : Length,
    VolumeUnit : Volume,
    Value : ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
    > VolumeUnit.volume(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    width: ScientificValue<PhysicalQuantity.Length, WidthUnit>,
    height: ScientificValue<PhysicalQuantity.Length, HeightUnit>,
    factory: (Decimal, VolumeUnit) -> Value
) = volume(SquareMeter.area(length, width), height, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.Pressure
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("volumeFromEnergyAndPressureDefault")
fun <
    EnergyUnit : Energy,
    PressureUnit : Pressure,
    VolumeUnit : Volume
    > VolumeUnit.volume(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = volume(energy, pressure, ::DefaultScientificValue)

@JvmName("volumeFromEnergyAndPressure")
fun <
    EnergyUnit : Energy,
    PressureUnit : Pressure,
    VolumeUnit : Volume,
    Value : ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
    > VolumeUnit.volume(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>,
    factory: (Decimal, VolumeUnit) -> Value
) = byDividing(energy, pressure, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("volumeFromMolarVolumeAndAmountOfSubstanceDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    VolumeUnit : Volume,
    MolarVolumeUnit : MolarVolume
    > VolumeUnit.volume(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>,
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = volume(molarVolume, amountOfSubstance, ::DefaultScientificValue)

@JvmName("volumeFromMolarVolumeAndAmountOfSubstance")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    VolumeUnit : Volume,
    MolarVolumeUnit : MolarVolume,
    Value : ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
    > VolumeUnit.volume(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>,
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    factory: (Decimal, VolumeUnit) -> Value
) = byMultiplying(molarVolume, amountOfSubstance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("volumeFromSpecificVolumeAndWeightDefault")
fun <
    VolumeUnit : Volume,
    SpecificVolumeUnit : SpecificVolume,
    WeightUnit : Weight
    > VolumeUnit.volume(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = volume(specificVolume, weight, ::DefaultScientificValue)

@JvmName("volumeFromSpecificVolumeAndWeight")
fun <
    VolumeUnit : Volume,
    SpecificVolumeUnit : SpecificVolume,
    WeightUnit : Weight,
    Value : ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
    > VolumeUnit.volume(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    factory: (Decimal, VolumeUnit) -> Value
) = byMultiplying(specificVolume, weight, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.VolumetricFlow
import kotlin.jvm.JvmName

@JvmName("volumeFromVolumetricFlowAndTimeDefault")
fun <
    VolumeUnit : Volume,
    TimeUnit : Time,
    VolumetricFlowUnit : VolumetricFlow
    > VolumeUnit.volume(
    volumetricFlow: ScientificValue<PhysicalQuantity.VolumetricFlow, VolumetricFlowUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = volume(volumetricFlow, time, ::DefaultScientificValue)

@JvmName("volumeFromVolumetricFlowAndTime")
fun <
    VolumeUnit : Volume,
    TimeUnit : Time,
    VolumetricFlowUnit : VolumetricFlow,
    Value : ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
    > VolumeUnit.volume(
    volumetricFlow: ScientificValue<PhysicalQuantity.VolumetricFlow, VolumetricFlowUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, VolumeUnit) -> Value
) = byMultiplying(volumetricFlow, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volume

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("volumeFromWeightAndDensityDefault")
fun <
    WeightUnit : Weight,
    VolumeUnit : Volume,
    DensityUnit : Density
    > VolumeUnit.volume(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = volume(weight, density, ::DefaultScientificValue)

@JvmName("volumeFromWeightAndDensity")
fun <
    WeightUnit : Weight,
    VolumeUnit : Volume,
    DensityUnit : Density,
    Value : ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
    > VolumeUnit.volume(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    factory: (Decimal, VolumeUnit) -> Value
) = byDividing(weight, density, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.amountOfSubstance.amountOfSubstance
import com.splendo.kaluga.scientific.converter.molarity.times
import com.splendo.kaluga.scientific.unit.MolarVolume
import com.splendo.kaluga.scientific.unit.Molarity
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("volumeTimesMolarity")
infix operator fun <MolarityUnit : Molarity, VolumeUnit : Volume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    molarity: ScientificValue<PhysicalQuantity.Molarity, MolarityUnit>
) = molarity * this

@JvmName("volumeDivMolarVolume")
infix operator fun <VolumeUnit : Volume, MolarVolumeUnit : MolarVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    molarVolume: ScientificValue<PhysicalQuantity.MolarVolume, MolarVolumeUnit>
) = molarVolume.unit.per.amountOfSubstance(this, molarVolume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.area
import com.splendo.kaluga.scientific.unit.Acre
import com.splendo.kaluga.scientific.unit.AcreFoot
import com.splendo.kaluga.scientific.unit.AcreInch
import com.splendo.kaluga.scientific.unit.Centimeter
import com.splendo.kaluga.scientific.unit.CubicCentimeter
import com.splendo.kaluga.scientific.unit.CubicDecameter
import com.splendo.kaluga.scientific.unit.CubicDecimeter
import com.splendo.kaluga.scientific.unit.CubicFoot
import com.splendo.kaluga.scientific.unit.CubicGigameter
import com.splendo.kaluga.scientific.unit.CubicHectometer
import com.splendo.kaluga.scientific.unit.CubicInch
import com.splendo.kaluga.scientific.unit.CubicKilometer
import com.splendo.kaluga.scientific.unit.CubicMegameter
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.CubicMicrometer
import com.splendo.kaluga.scientific.unit.CubicMile
import com.splendo.kaluga.scientific.unit.CubicMillimeter
import com.splendo.kaluga.scientific.unit.CubicNanometer
import com.splendo.kaluga.scientific.unit.CubicYard
import com.splendo.kaluga.scientific.unit.Decameter
import com.splendo.kaluga.scientific.unit.Decimeter
import com.splendo.kaluga.scientific.unit.Foot
import com.splendo.kaluga.scientific.unit.Gigameter
import com.splendo.kaluga.scientific.unit.Hectometer
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.ImperialVolume
import com.splendo.kaluga.scientific.unit.Inch
import com.splendo.kaluga.scientific.unit.Kilometer
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.Megameter
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.MetricVolume
import com.splendo.kaluga.scientific.unit.Micrometer
import com.splendo.kaluga.scientific.unit.Mile
import com.splendo.kaluga.scientific.unit.Millimeter
import com.splendo.kaluga.scientific.unit.Nanometer
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.SquareDecameter
import com.splendo.kaluga.scientific.unit.SquareDecimeter
import com.splendo.kaluga.scientific.unit.SquareFoot
import com.splendo.kaluga.scientific.unit.SquareGigameter
import com.splendo.kaluga.scientific.unit.SquareHectometer
import com.splendo.kaluga.scientific.unit.SquareInch
import com.splendo.kaluga.scientific.unit.SquareKilometer
import com.splendo.kaluga.scientific.unit.SquareMegameter
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.SquareMicrometer
import com.splendo.kaluga.scientific.unit.SquareMile
import com.splendo.kaluga.scientific.unit.SquareMillimeter
import com.splendo.kaluga.scientific.unit.SquareNanometer
import com.splendo.kaluga.scientific.unit.SquareYard
import com.splendo.kaluga.scientific.unit.UKImperialVolume
import com.splendo.kaluga.scientific.unit.USCustomaryVolume
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.Yard
import kotlin.jvm.JvmName

@JvmName("cubicMeterDivMeter")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicMeter>.div(height: ScientificValue<PhysicalQuantity.Length, Meter>) =
    SquareMeter.area(this, height)

@JvmName("cubicNanometerDivNanometer")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicNanometer>.div(height: ScientificValue<PhysicalQuantity.Length, Nanometer>) =
    SquareNanometer.area(this, height)

@JvmName("cubicMicrometerDivMicrometer")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicMicrometer>.div(height: ScientificValue<PhysicalQuantity.Length, Micrometer>) =
    SquareMicrometer.area(this, height)

@JvmName("cubicMillimeterDivMillimeter")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicMillimeter>.div(height: ScientificValue<PhysicalQuantity.Length, Millimeter>) =
    SquareMillimeter.area(this, height)

@JvmName("cubicCentimeterDivCentimeter")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicCentimeter>.div(height: ScientificValue<PhysicalQuantity.Length, Centimeter>) =
    SquareCentimeter.area(this, height)

@JvmName("cubicDecimeterDivDecimeter")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicDecimeter>.div(height: ScientificValue<PhysicalQuantity.Length, Decimeter>) =
    SquareDecimeter.area(this, height)

@JvmName("cubicDecameterDivDecameter")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicDecameter>.div(height: ScientificValue<PhysicalQuantity.Length, Decameter>) =
    SquareDecameter.area(this, height)

@JvmName("cubicHectometerDivHectometer")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicHectometer>.div(height: ScientificValue<PhysicalQuantity.Length, Hectometer>) =
    SquareHectometer.area(this, height)

@JvmName("cubicKilometerDivKilometer")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicKilometer>.div(height: ScientificValue<PhysicalQuantity.Length, Kilometer>) =
    SquareKilometer.area(this, height)

@JvmName("cubicMegameterDivMegameter")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicMegameter>.div(height: ScientificValue<PhysicalQuantity.Length, Megameter>) =
    SquareMegameter.area(this, height)

@JvmName("cubicGigameterDivGigameter")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicGigameter>.div(height: ScientificValue<PhysicalQuantity.Length, Gigameter>) =
    SquareGigameter.area(this, height)

@JvmName("metricVolumeDivMetricLength")
infix operator fun <VolumeUnit : MetricVolume, HeightUnit : MetricLength> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    height: ScientificValue<PhysicalQuantity.Length, HeightUnit>
) = SquareMeter.area(this, height)

@JvmName("cubicInchDivInch")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicInch>.div(height: ScientificValue<PhysicalQuantity.Length, Inch>) =
    SquareInch.area(this, height)

@JvmName("cubicFootDivFoot")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicFoot>.div(height: ScientificValue<PhysicalQuantity.Length, Foot>) =
    SquareFoot.area(this, height)

@JvmName("cubicYardDivYard")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicYard>.div(height: ScientificValue<PhysicalQuantity.Length, Yard>) =
    SquareYard.area(this, height)

@JvmName("cubicMileDivMile")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicMile>.div(height: ScientificValue<PhysicalQuantity.Length, Mile>) =
    SquareMile.area(this, height)

@JvmName("acreInchDivInch")
infix operator fun ScientificValue<PhysicalQuantity.Volume, AcreInch>.div(height: ScientificValue<PhysicalQuantity.Length, Inch>) =
    Acre.area(this, height)

@JvmName("acreFootDivFoot")
infix operator fun ScientificValue<PhysicalQuantity.Volume, AcreFoot>.div(height: ScientificValue<PhysicalQuantity.Length, Foot>) =
    Acre.area(this, height)

@JvmName("imperialVolumeDivImperialLength")
infix operator fun <VolumeUnit : ImperialVolume, HeightUnit : ImperialLength> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    height: ScientificValue<PhysicalQuantity.Length, HeightUnit>
) = SquareFoot.area(this, height)

@JvmName("ukImperialVolumeDivImperialLength")
infix operator fun <VolumeUnit : UKImperialVolume, HeightUnit : ImperialLength> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    height: ScientificValue<PhysicalQuantity.Length, HeightUnit>
) = SquareFoot.area(this, height)

@JvmName("usCustomaryVolumeDivImperialLength")
infix operator fun <VolumeUnit : USCustomaryVolume, HeightUnit : ImperialLength> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    height: ScientificValue<PhysicalQuantity.Length, HeightUnit>
) = SquareFoot.area(this, height)

@JvmName("volumeDivLength")
infix operator fun <VolumeUnit : Volume, HeightUnit : Length> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    height: ScientificValue<PhysicalQuantity.Length, HeightUnit>
) = SquareMeter.area(this, height)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.pressure.times
import com.splendo.kaluga.scientific.unit.Barye
import com.splendo.kaluga.scientific.unit.CubicCentimeter
import com.splendo.kaluga.scientific.unit.CubicFoot
import com.splendo.kaluga.scientific.unit.CubicInch
import com.splendo.kaluga.scientific.unit.ImperialPressure
import com.splendo.kaluga.scientific.unit.ImperialTonSquareInch
import com.splendo.kaluga.scientific.unit.ImperialVolume
import com.splendo.kaluga.scientific.unit.KiloPoundSquareInch
import com.splendo.kaluga.scientific.unit.KipSquareInch
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.OunceSquareInch
import com.splendo.kaluga.scientific.unit.PoundSquareFoot
import com.splendo.kaluga.scientific.unit.PoundSquareInch
import com.splendo.kaluga.scientific.unit.Pressure
import com.splendo.kaluga.scientific.unit.UKImperialPressure
import com.splendo.kaluga.scientific.unit.UKImperialVolume
import com.splendo.kaluga.scientific.unit.USCustomaryPressure
import com.splendo.kaluga.scientific.unit.USCustomaryVolume
import com.splendo.kaluga.scientific.unit.USTonSquareInch
import com.splendo.kaluga.scientific.unit.Volume
import kotlin.jvm.JvmName

@JvmName("cubicCentimeterTimesBarye")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicCentimeter>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, Barye>) =
    pressure * this

@JvmName("cubicCentimeterTimesBaryeMultiple")
infix operator fun <BaryeUnit> ScientificValue<PhysicalQuantity.Volume, CubicCentimeter>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, BaryeUnit>
) where BaryeUnit : Pressure, BaryeUnit : MetricMultipleUnit<MeasurementSystem.Metric, PhysicalQuantity.Pressure, Barye> =
    pressure * this

@JvmName("cubicFootTimesPoundSquareFoot")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicFoot>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, PoundSquareFoot>) =
    pressure * this

@JvmName("cubicInchTimesPoundSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicInch>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, PoundSquareInch>) =
    pressure * this

@JvmName("cubicInchTimesOunceSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicInch>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, OunceSquareInch>) =
    pressure * this

@JvmName("cubicInchTimesKilopoundSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicInch>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, KiloPoundSquareInch>) =
    pressure * this

@JvmName("cubicInchTimesKipSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicInch>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, KipSquareInch>) =
    pressure * this

@JvmName("cubicInchTimesUSTonSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicInch>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, USTonSquareInch>) =
    pressure * this

@JvmName("cubicInchTimesImperialTonSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicInch>.times(pressure: ScientificValue<PhysicalQuantity.Pressure, ImperialTonSquareInch>) =
    pressure * this

@JvmName("imperialVolumeTimesImperialPressure")
infix operator fun <PressureUnit : ImperialPressure, VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = pressure * this

@JvmName("imperialVolumeTimesUKImperialPressure")
infix operator fun <PressureUnit : UKImperialPressure, VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = pressure * this

@JvmName("imperialVolumeTimesUSCustomaryPressure")
infix operator fun <PressureUnit : USCustomaryPressure, VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = pressure * this

@JvmName("ukImperialVolumeTimesImperialPressure")
infix operator fun <PressureUnit : ImperialPressure, VolumeUnit : UKImperialVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = pressure * this

@JvmName("ukImperialVolumeTimesUKImperialPressure")
infix operator fun <PressureUnit : UKImperialPressure, VolumeUnit : UKImperialVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = pressure * this

@JvmName("usCustomaryVolumeTimesImperialPressure")
infix operator fun <PressureUnit : ImperialPressure, VolumeUnit : USCustomaryVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = pressure * this

@JvmName("usCustomaryVolumeTimesUSCustomaryPressure")
infix operator fun <PressureUnit : USCustomaryPressure, VolumeUnit : USCustomaryVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = pressure * this

@JvmName("volumeTimesPressure")
infix operator fun <PressureUnit : Pressure, VolumeUnit : Volume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    pressure: ScientificValue<PhysicalQuantity.Pressure, PressureUnit>
) = pressure * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.length.height
import com.splendo.kaluga.scientific.unit.Acre
import com.splendo.kaluga.scientific.unit.AcreFoot
import com.splendo.kaluga.scientific.unit.AcreInch
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.Centimeter
import com.splendo.kaluga.scientific.unit.CubicCentimeter
import com.splendo.kaluga.scientific.unit.CubicDecameter
import com.splendo.kaluga.scientific.unit.CubicDecimeter
import com.splendo.kaluga.scientific.unit.CubicFoot
import com.splendo.kaluga.scientific.unit.CubicGigameter
import com.splendo.kaluga.scientific.unit.CubicHectometer
import com.splendo.kaluga.scientific.unit.CubicInch
import com.splendo.kaluga.scientific.unit.CubicKilometer
import com.splendo.kaluga.scientific.unit.CubicMegameter
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.CubicMicrometer
import com.splendo.kaluga.scientific.unit.CubicMile
import com.splendo.kaluga.scientific.unit.CubicMillimeter
import com.splendo.kaluga.scientific.unit.CubicNanometer
import com.splendo.kaluga.scientific.unit.CubicYard
import com.splendo.kaluga.scientific.unit.Decameter
import com.splendo.kaluga.scientific.unit.Decimeter
import com.splendo.kaluga.scientific.unit.Foot
import com.splendo.kaluga.scientific.unit.Gigameter
import com.splendo.kaluga.scientific.unit.Hectometer
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialVolume
import com.splendo.kaluga.scientific.unit.Inch
import com.splendo.kaluga.scientific.unit.Kilometer
import com.splendo.kaluga.scientific.unit.Megameter
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricVolume
import com.splendo.kaluga.scientific.unit.Micrometer
import com.splendo.kaluga.scientific.unit.Mile
import com.splendo.kaluga.scientific.unit.Millimeter
import com.splendo.kaluga.scientific.unit.Nanometer
import com.splendo.kaluga.scientific.unit.SquareCentimeter
import com.splendo.kaluga.scientific.unit.SquareDecameter
import com.splendo.kaluga.scientific.unit.SquareDecimeter
import com.splendo.kaluga.scientific.unit.SquareFoot
import com.splendo.kaluga.scientific.unit.SquareGigameter
import com.splendo.kaluga.scientific.unit.SquareHectometer
import com.splendo.kaluga.scientific.unit.SquareInch
import com.splendo.kaluga.scientific.unit.SquareKilometer
import com.splendo.kaluga.scientific.unit.SquareMegameter
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.SquareMicrometer
import com.splendo.kaluga.scientific.unit.SquareMile
import com.splendo.kaluga.scientific.unit.SquareMillimeter
import com.splendo.kaluga.scientific.unit.SquareNanometer
import com.splendo.kaluga.scientific.unit.SquareYard
import com.splendo.kaluga.scientific.unit.UKImperialVolume
import com.splendo.kaluga.scientific.unit.USCustomaryVolume
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.Yard
import kotlin.jvm.JvmName

@JvmName("cubicMeterDivSquareMeter")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicMeter>.div(area: ScientificValue<PhysicalQuantity.Area, SquareMeter>) =
    Meter.height(this, area)

@JvmName("cubicNanometerDivSquareNanometer")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicNanometer>.div(area: ScientificValue<PhysicalQuantity.Area, SquareNanometer>) =
    Nanometer.height(this, area)

@JvmName("cubicMicrometerDivSquareMicrometer")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicMicrometer>.div(area: ScientificValue<PhysicalQuantity.Area, SquareMicrometer>) =
    Micrometer.height(this, area)

@JvmName("cubicMillimeterDivSquareMillimeter")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicMillimeter>.div(area: ScientificValue<PhysicalQuantity.Area, SquareMillimeter>) =
    Millimeter.height(this, area)

@JvmName("cubicCentimeterDivSquareCentimeter")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicCentimeter>.div(area: ScientificValue<PhysicalQuantity.Area, SquareCentimeter>) =
    Centimeter.height(this, area)

@JvmName("cubicDecimeterDivSquareDecimeter")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicDecimeter>.div(area: ScientificValue<PhysicalQuantity.Area, SquareDecimeter>) =
    Decimeter.height(this, area)

@JvmName("cubicDecameterDivSquareDecameter")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicDecameter>.div(area: ScientificValue<PhysicalQuantity.Area, SquareDecameter>) =
    Decameter.height(this, area)

@JvmName("cubicHectometerDivSquareHectometer")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicHectometer>.div(area: ScientificValue<PhysicalQuantity.Area, SquareHectometer>) =
    Hectometer.height(this, area)

@JvmName("cubicKilometerDivSquareKilometer")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicKilometer>.div(area: ScientificValue<PhysicalQuantity.Area, SquareKilometer>) =
    Kilometer.height(this, area)

@JvmName("cubicMegameterDivSquareMegameter")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicMegameter>.div(area: ScientificValue<PhysicalQuantity.Area, SquareMegameter>) =
    Megameter.height(this, area)

@JvmName("cubicGigameterDivSquareGigameter")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicGigameter>.div(area: ScientificValue<PhysicalQuantity.Area, SquareGigameter>) =
    Gigameter.height(this, area)

@JvmName("metricVolumeDivMetricArea")
infix operator fun <VolumeUnit : MetricVolume, AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Meter.height(this, area)

@JvmName("cubicInchDivSquareInch")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicInch>.div(area: ScientificValue<PhysicalQuantity.Area, SquareInch>) =
    Inch.height(this, area)

@JvmName("cubicFootDivSquareFoot")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicFoot>.div(area: ScientificValue<PhysicalQuantity.Area, SquareFoot>) =
    Foot.height(this, area)

@JvmName("cubicYardDivSquareYard")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicYard>.div(area: ScientificValue<PhysicalQuantity.Area, SquareYard>) =
    Yard.height(this, area)

@JvmName("cubicMileDivSquareMile")
infix operator fun ScientificValue<PhysicalQuantity.Volume, CubicMile>.div(area: ScientificValue<PhysicalQuantity.Area, SquareMile>) =
    Mile.height(this, area)

@JvmName("acreFootDivAcre")
infix operator fun ScientificValue<PhysicalQuantity.Volume, AcreFoot>.div(area: ScientificValue<PhysicalQuantity.Area, Acre>) =
    Foot.height(this, area)

@JvmName("acreInchDivAcre")
infix operator fun ScientificValue<PhysicalQuantity.Volume, AcreInch>.div(area: ScientificValue<PhysicalQuantity.Area, Acre>) =
    Inch.height(this, area)

@JvmName("imperialVolumeDivImperialArea")
infix operator fun <VolumeUnit : ImperialVolume, AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Foot.height(this, area)

@JvmName("ukImperialVolumeDivImperialArea")
infix operator fun <VolumeUnit : UKImperialVolume, AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Foot.height(this, area)

@JvmName("usCustomaryVolumeDivImperialArea")
infix operator fun <VolumeUnit : USCustomaryVolume, AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Foot.height(this, area)

@JvmName("volumeDivArea")
infix operator fun <VolumeUnit : Volume, AreaUnit : Area> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = Meter.height(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarVolume.molarVolume
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialVolume
import com.splendo.kaluga.scientific.unit.MetricVolume
import com.splendo.kaluga.scientific.unit.UKImperialVolume
import com.splendo.kaluga.scientific.unit.USCustomaryVolume
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricVolumeDivAmountOfSubstance")
infix operator fun <VolumeUnit : MetricVolume, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (unit per amountOfSubstance.unit).molarVolume(this, amountOfSubstance)

@JvmName("imperialVolumeDivAmountOfSubstance")
infix operator fun <VolumeUnit : ImperialVolume, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (unit per amountOfSubstance.unit).molarVolume(this, amountOfSubstance)

@JvmName("ukImperialVolumeDivAmountOfSubstance")
infix operator fun <VolumeUnit : UKImperialVolume, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (unit per amountOfSubstance.unit).molarVolume(this, amountOfSubstance)

@JvmName("usCustomaryVolumeDivAmountOfSubstance")
infix operator fun <VolumeUnit : USCustomaryVolume, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (unit per amountOfSubstance.unit).molarVolume(this, amountOfSubstance)

@JvmName("volumeDivAmountOfSubstance")
infix operator fun <VolumeUnit : Volume, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (CubicMeter per amountOfSubstance.unit).molarVolume(this, amountOfSubstance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.specificVolume.specificVolume
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialVolume
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricVolume
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.UKImperialVolume
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryVolume
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricVolumeDivMetricWeight")
infix operator fun <VolumeUnit : MetricVolume, WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificVolume(this, weight)

@JvmName("imperialVolumeDivImperialWeight")
infix operator fun <VolumeUnit : ImperialVolume, WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificVolume(this, weight)

@JvmName("imperialVolumeDivUKImperialWeight")
infix operator fun <VolumeUnit : ImperialVolume, WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificVolume(this, weight)

@JvmName("imperialVolumeDivUSCustomaryWeight")
infix operator fun <VolumeUnit : ImperialVolume, WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificVolume(this, weight)

@JvmName("ukImperialVolumeDivImperialWeight")
infix operator fun <VolumeUnit : UKImperialVolume, WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificVolume(this, weight)

@JvmName("ukImperialVolumeDivUKImperialWeight")
infix operator fun <VolumeUnit : UKImperialVolume, WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificVolume(this, weight)

@JvmName("usCustomaryVolumeDivImperialWeight")
infix operator fun <VolumeUnit : USCustomaryVolume, WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificVolume(this, weight)

@JvmName("usCustomaryVolumeDivUSCustomaryWeight")
infix operator fun <VolumeUnit : USCustomaryVolume, WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (unit per weight.unit).specificVolume(this, weight)

@JvmName("volumeDivWeight")
infix operator fun <VolumeUnit : Volume, WeightUnit : Weight> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = (CubicMeter per Kilogram).specificVolume(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.volumetricFlow.volumetricFlow
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialVolume
import com.splendo.kaluga.scientific.unit.MetricVolume
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.UKImperialVolume
import com.splendo.kaluga.scientific.unit.USCustomaryVolume
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricVolumeDivTime")
infix operator fun <VolumeUnit : MetricVolume, TimeUnit : Time> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).volumetricFlow(this, time)

@JvmName("imperialVolumeDivTime")
infix operator fun <VolumeUnit : ImperialVolume, TimeUnit : Time> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).volumetricFlow(this, time)

@JvmName("ukImperialVolumeDivTime")
infix operator fun <VolumeUnit : UKImperialVolume, TimeUnit : Time> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).volumetricFlow(this, time)

@JvmName("usCustomaryVolumeDivTime")
infix operator fun <VolumeUnit : USCustomaryVolume, TimeUnit : Time> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).volumetricFlow(this, time)

@JvmName("volumeDivTime")
infix operator fun <VolumeUnit : Volume, TimeUnit : Time> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (CubicMeter per Second).volumetricFlow(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volume

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.density.times
import com.splendo.kaluga.scientific.converter.weight.weight
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.ImperialVolume
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.MetricVolume
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialVolume
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomaryVolume
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.ukImperial
import com.splendo.kaluga.scientific.unit.usCustomary
import kotlin.jvm.JvmName

@JvmName("metricVolumeTimesMetricDensity")
infix operator fun <VolumeUnit : MetricVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, MetricDensity>
) = density * this

@JvmName("imperialVolumeTimesImperialDensity")
infix operator fun <VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>
) = density * this

@JvmName("imperialVolumeTimesUKImperialDensity")
infix operator fun <VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, UKImperialDensity>
) = density * this

@JvmName("imperialVolumeTimesUSCustomaryDensity")
infix operator fun <VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>
) = density * this

@JvmName("ukImperialVolumeTimesImperialDensity")
infix operator fun <VolumeUnit : UKImperialVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>
) = density * this

@JvmName("ukImperialVolumeTimesUKImperialDensity")
infix operator fun <VolumeUnit : UKImperialVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, UKImperialDensity>
) = density * this

@JvmName("usCustomaryVolumeTimesImperialDensity")
infix operator fun <VolumeUnit : USCustomaryVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>
) = density * this

@JvmName("usCustomaryVolumeTimesUSCustomaryDensity")
infix operator fun <VolumeUnit : USCustomaryVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>
) = density * this

@JvmName("volumeTimesDensity")
infix operator fun <DensityUnit : Density, VolumeUnit : Volume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.times(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = density * this

@JvmName("metricVolumeDivMetricSpecificVolume")
infix operator fun <VolumeUnit : MetricVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>
) = specificVolume.unit.per.weight(this, specificVolume)

@JvmName("imperialVolumeDivImperialSpecificVolume")
infix operator fun <VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = specificVolume.unit.per.weight(this, specificVolume)

@JvmName("imperialVolumeDivUKImperialSpecificVolume")
infix operator fun <VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>
) = specificVolume.unit.per.weight(this, specificVolume)

@JvmName("imperialVolumeDivUSCustomarySpecificVolume")
infix operator fun <VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>
) = specificVolume.unit.per.weight(this, specificVolume)

@JvmName("ukImperialVolumeDivImperialSpecificVolume")
infix operator fun <VolumeUnit : UKImperialVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = specificVolume.unit.per.ukImperial.weight(this, specificVolume)

@JvmName("ukImperialVolumeDivUKImperialSpecificVolume")
infix operator fun <VolumeUnit : UKImperialVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>
) = specificVolume.unit.per.weight(this, specificVolume)

@JvmName("usCustomaryVolumeDivImperialSpecificVolume")
infix operator fun <VolumeUnit : USCustomaryVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = specificVolume.unit.per.usCustomary.weight(this, specificVolume)

@JvmName("usCustomaryVolumeDivUSCustomarySpecificVolume")
infix operator fun <VolumeUnit : USCustomaryVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>
) = specificVolume.unit.per.weight(this, specificVolume)

@JvmName("volumeDivSpecificVolume")
infix operator fun <VolumeUnit : Volume, SpecificVolumeUnit : SpecificVolume> ScientificValue<PhysicalQuantity.Volume, VolumeUnit>.div(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = Kilogram.weight(this, specificVolume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volumetricFlow

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.VolumetricFlow
import kotlin.jvm.JvmName

@JvmName("volumetricFlowFromVolumeAndTimeDefault")
fun <
    VolumeUnit : Volume,
    TimeUnit : Time,
    VolumetricFlowUnit : VolumetricFlow
    > VolumetricFlowUnit.volumetricFlow(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = volumetricFlow(volume, time, ::DefaultScientificValue)

@JvmName("volumetricFlowFromVolumeAndTime")
fun <
    VolumeUnit : Volume,
    TimeUnit : Time,
    VolumetricFlowUnit : VolumetricFlow,
    Value : ScientificValue<PhysicalQuantity.VolumetricFlow, VolumetricFlowUnit>
    > VolumetricFlowUnit.volumetricFlow(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, VolumetricFlowUnit) -> Value
) = byDividing(volume, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volumetricFlow

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.VolumetricFlow
import com.splendo.kaluga.scientific.unit.VolumetricFlux
import kotlin.jvm.JvmName

@JvmName("volumetricFlowFromVolumetricFluxAndAreaDefault")
fun <
    VolumetricFlowUnit : VolumetricFlow,
    AreaUnit : Area,
    VolumetricFluxUnit : VolumetricFlux
    > VolumetricFlowUnit.volumetricFlow(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, VolumetricFluxUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = volumetricFlow(volumetricFlux, area, ::DefaultScientificValue)

@JvmName("volumetricFlowFromVolumetricFluxAndArea")
fun <
    VolumetricFlowUnit : VolumetricFlow,
    AreaUnit : Area,
    VolumetricFluxUnit : VolumetricFlux,
    Value : ScientificValue<PhysicalQuantity.VolumetricFlow, VolumetricFlowUnit>
    > VolumetricFlowUnit.volumetricFlow(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, VolumetricFluxUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, VolumetricFlowUnit) -> Value
) = byMultiplying(volumetricFlux, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volumetricFlow

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.area
import com.splendo.kaluga.scientific.unit.ImperialVolumetricFlow
import com.splendo.kaluga.scientific.unit.ImperialVolumetricFlux
import com.splendo.kaluga.scientific.unit.MetricVolumetricFlow
import com.splendo.kaluga.scientific.unit.MetricVolumetricFlux
import com.splendo.kaluga.scientific.unit.UKImperialVolumetricFlow
import com.splendo.kaluga.scientific.unit.UKImperialVolumetricFlux
import com.splendo.kaluga.scientific.unit.USCustomaryVolumetricFlow
import com.splendo.kaluga.scientific.unit.USCustomaryVolumetricFlux
import com.splendo.kaluga.scientific.unit.VolumetricFlow
import com.splendo.kaluga.scientific.unit.VolumetricFlux
import kotlin.jvm.JvmName

@JvmName("metricVolumetricFlowDivMetricVolumetricFlux")
infix operator fun ScientificValue<PhysicalQuantity.VolumetricFlow, MetricVolumetricFlow>.div(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, MetricVolumetricFlux>
) = volumetricFlux.unit.per.area(this, volumetricFlux)

@JvmName("imperialVolumetricFlowDivImperialVolumetricFlux")
infix operator fun ScientificValue<PhysicalQuantity.VolumetricFlow, ImperialVolumetricFlow>.div(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, ImperialVolumetricFlux>
) = volumetricFlux.unit.per.area(this, volumetricFlux)

@JvmName("imperialVolumetricFlowDivUKImperialVolumetricFlux")
infix operator fun ScientificValue<PhysicalQuantity.VolumetricFlow, ImperialVolumetricFlow>.div(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, UKImperialVolumetricFlux>
) = volumetricFlux.unit.per.area(this, volumetricFlux)

@JvmName("imperialVolumetricFlowDivUSCustomaryVolumetricFlux")
infix operator fun ScientificValue<PhysicalQuantity.VolumetricFlow, ImperialVolumetricFlow>.div(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, USCustomaryVolumetricFlux>
) = volumetricFlux.unit.per.area(this, volumetricFlux)

@JvmName("ukImperialVolumetricFlowDivImperialVolumetricFlux")
infix operator fun ScientificValue<PhysicalQuantity.VolumetricFlow, UKImperialVolumetricFlow>.div(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, ImperialVolumetricFlux>
) = volumetricFlux.unit.per.area(this, volumetricFlux)

@JvmName("ukImperialVolumetricFlowDivUKImperialVolumetricFlux")
infix operator fun ScientificValue<PhysicalQuantity.VolumetricFlow, UKImperialVolumetricFlow>.div(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, UKImperialVolumetricFlux>
) = volumetricFlux.unit.per.area(this, volumetricFlux)

@JvmName("usCustomaryVolumetricFlowDivImperialVolumetricFlux")
infix operator fun ScientificValue<PhysicalQuantity.VolumetricFlow, USCustomaryVolumetricFlow>.div(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, ImperialVolumetricFlux>
) = volumetricFlux.unit.per.area(this, volumetricFlux)

@JvmName("usCustomaryVolumetricFlowDivUSCustomaryVolumetricFlux")
infix operator fun ScientificValue<PhysicalQuantity.VolumetricFlow, USCustomaryVolumetricFlow>.div(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, USCustomaryVolumetricFlux>
) = volumetricFlux.unit.per.area(this, volumetricFlux)

@JvmName("volumetricFlowDivVolumetricFlux")
infix operator fun <VolumetricFlowUnit : VolumetricFlow, VolumetricFluxUnit : VolumetricFlux> ScientificValue<PhysicalQuantity.VolumetricFlow, VolumetricFlowUnit>.div(
    volumetricFlux: ScientificValue<PhysicalQuantity.VolumetricFlux, VolumetricFluxUnit>
) = volumetricFlux.unit.per.area(this, volumetricFlux)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volumetricFlow

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.volume.volume
import com.splendo.kaluga.scientific.unit.ImperialVolumetricFlow
import com.splendo.kaluga.scientific.unit.MetricVolumetricFlow
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.UKImperialVolumetricFlow
import com.splendo.kaluga.scientific.unit.USCustomaryVolumetricFlow
import com.splendo.kaluga.scientific.unit.VolumetricFlow
import kotlin.jvm.JvmName

@JvmName("metricVolumetricFlowTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.VolumetricFlow, MetricVolumetricFlow>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.volume.volume(this, time)

@JvmName("imperialVolumetricFlowTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.VolumetricFlow, ImperialVolumetricFlow>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.volume.volume(this, time)

@JvmName("ukImperialVolumetricFlowTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.VolumetricFlow, UKImperialVolumetricFlow>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.volume.volume(this, time)

@JvmName("usCustomaryVolumetricFlowTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.VolumetricFlow, USCustomaryVolumetricFlow>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.volume.volume(this, time)

@JvmName("volumetricFlowTimesTime")
infix operator fun <VolumetricFlowUnit : VolumetricFlow, TimeUnit : Time> ScientificValue<PhysicalQuantity.VolumetricFlow, VolumetricFlowUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = unit.volume.volume(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volumetricFlow

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.volumetricFlux.volumetricFlux
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialVolumetricFlow
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricVolumetricFlow
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.UKImperialVolumetricFlow
import com.splendo.kaluga.scientific.unit.USCustomaryVolumetricFlow
import com.splendo.kaluga.scientific.unit.VolumetricFlow
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricVolumetricFlowDivMetricArea")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.VolumetricFlow, MetricVolumetricFlow>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit per area.unit).volumetricFlux(this, area)

@JvmName("imperialVolumetricFlowDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.VolumetricFlow, ImperialVolumetricFlow>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit per area.unit).volumetricFlux(this, area)

@JvmName("ukImperialVolumetricFlowDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.VolumetricFlow, UKImperialVolumetricFlow>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit per area.unit).volumetricFlux(this, area)

@JvmName("usCustomaryVolumetricFlowDivImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.VolumetricFlow, USCustomaryVolumetricFlow>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit per area.unit).volumetricFlux(this, area)

@JvmName("volumetricFlowDivArea")
infix operator fun <VolumetricFlowUnit : VolumetricFlow, AreaUnit : Area> ScientificValue<PhysicalQuantity.VolumetricFlow, VolumetricFlowUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (CubicMeter per Second per SquareMeter).volumetricFlux(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volumetricFlux

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.VolumetricFlow
import com.splendo.kaluga.scientific.unit.VolumetricFlux
import kotlin.jvm.JvmName

@JvmName("volumetricFluxFromVolumetricFlowAndAreaDefault")
fun <
    VolumetricFlowUnit : VolumetricFlow,
    AreaUnit : Area,
    VolumetricFluxUnit : VolumetricFlux
    > VolumetricFluxUnit.volumetricFlux(
    volumetricFlow: ScientificValue<PhysicalQuantity.VolumetricFlow, VolumetricFlowUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = volumetricFlux(volumetricFlow, area, ::DefaultScientificValue)

@JvmName("volumetricFluxFromVolumetricFlowAndArea")
fun <
    VolumetricFlowUnit : VolumetricFlow,
    AreaUnit : Area,
    VolumetricFluxUnit : VolumetricFlux,
    Value : ScientificValue<PhysicalQuantity.VolumetricFlux, VolumetricFluxUnit>
    > VolumetricFluxUnit.volumetricFlux(
    volumetricFlow: ScientificValue<PhysicalQuantity.VolumetricFlow, VolumetricFlowUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, VolumetricFluxUnit) -> Value
) = byDividing(volumetricFlow, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.volumetricFlux

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.volumetricFlow.volumetricFlow
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialVolumetricFlux
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricVolumetricFlux
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.UKImperialVolumetricFlux
import com.splendo.kaluga.scientific.unit.USCustomaryVolumetricFlux
import com.splendo.kaluga.scientific.unit.VolumetricFlux
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricVolumetricFluxTimesMetricArea")
infix operator fun <AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.VolumetricFlux, MetricVolumetricFlux>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit.volumetricFlow).volumetricFlow(this, area)

@JvmName("imperialVolumetricFluxTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.VolumetricFlux, ImperialVolumetricFlux>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit.volumetricFlow).volumetricFlow(this, area)

@JvmName("ukImperialVolumetricFluxTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.VolumetricFlux, UKImperialVolumetricFlux>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit.volumetricFlow).volumetricFlow(this, area)

@JvmName("usCustomaryVolumetricFluxTimesImperialArea")
infix operator fun <AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.VolumetricFlux, USCustomaryVolumetricFlux>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit.volumetricFlow).volumetricFlow(this, area)

@JvmName("volumetricFluxTimesArea")
infix operator fun <VolumetricFluxUnit : VolumetricFlux, AreaUnit : Area> ScientificValue<PhysicalQuantity.VolumetricFlux, VolumetricFluxUnit>.times(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (CubicMeter per Second).volumetricFlow(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("weightFromAmountOfSubstanceAndMolalityDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    WeightUnit : Weight,
    MolalityUnit : Molality
    > WeightUnit.weight(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = weight(amountOfSubstance, molality, ::DefaultScientificValue)

@JvmName("weightFromAmountOfSubstanceAndMolality")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    WeightUnit : Weight,
    MolalityUnit : Molality,
    Value : ScientificValue<PhysicalQuantity.Weight, WeightUnit>
    > WeightUnit.weight(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>,
    factory: (Decimal, WeightUnit) -> Value
) = byDividing(amountOfSubstance, molality, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("weightFromAreaDensityAndAreaDefault")
fun <
    WeightUnit : Weight,
    AreaUnit : Area,
    AreaDensityUnit : AreaDensity
    > WeightUnit.mass(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = mass(areaDensity, area, ::DefaultScientificValue)

@JvmName("weightFromAreaDensityAndArea")
fun <
    WeightUnit : Weight,
    AreaUnit : Area,
    AreaDensityUnit : AreaDensity,
    Value : ScientificValue<PhysicalQuantity.Weight, WeightUnit>
    > WeightUnit.mass(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>,
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>,
    factory: (Decimal, WeightUnit) -> Value
) = byMultiplying(areaDensity, area, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("weightFromDensityAndVolumeDefault")
fun <
    MassUnit : Weight,
    DensityUnit : Density,
    VolumeUnit : Volume
    > MassUnit.mass(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = mass(density, volume, ::DefaultScientificValue)

@JvmName("weightFromDensityAndVolume")
fun <
    MassUnit : Weight,
    DensityUnit : Density,
    VolumeUnit : Volume,
    Value : ScientificValue<PhysicalQuantity.Weight, MassUnit>
    > MassUnit.mass(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>,
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    factory: (Decimal, MassUnit) -> Value
) = byMultiplying(density, volume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.IonizingRadiationAbsorbedDose
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("weightFromEnergyAndAbsorbedDoseDefault")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    AbsorbedDoseUnit : IonizingRadiationAbsorbedDose
    > WeightUnit.weight(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>
) = weight(energy, absorbedDose, ::DefaultScientificValue)

@JvmName("weightFromEnergyAndAbsorbedDose")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    AbsorbedDoseUnit : IonizingRadiationAbsorbedDose,
    Value : ScientificValue<PhysicalQuantity.Weight, WeightUnit>
    > WeightUnit.weight(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>,
    factory: (Decimal, WeightUnit) -> Value
) = byDividing(energy, absorbedDose, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.IonizingRadiationEquivalentDose
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("weightFromEnergyAndEquivalentDoseDefault")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    EquivalentDoseUnit : IonizingRadiationEquivalentDose
    > WeightUnit.weight(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>
) = weight(energy, equivalentDose, ::DefaultScientificValue)

@JvmName("weightFromEnergyAndEquivalentDose")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    EquivalentDoseUnit : IonizingRadiationEquivalentDose,
    Value : ScientificValue<PhysicalQuantity.Weight, WeightUnit>
    > WeightUnit.weight(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>,
    factory: (Decimal, WeightUnit) -> Value
) = byDividing(energy, equivalentDose, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Energy
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("weightFromEnergyAndSpecificEnergyDefault")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    SpecificEnergyUnit : SpecificEnergy
    > WeightUnit.weight(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>
) = weight(energy, specificEnergy, ::DefaultScientificValue)

@JvmName("weightFromEnergyAndSpecificEnergy")
fun <
    EnergyUnit : Energy,
    WeightUnit : Weight,
    SpecificEnergyUnit : SpecificEnergy,
    Value : ScientificValue<PhysicalQuantity.Weight, WeightUnit>
    > WeightUnit.weight(
    energy: ScientificValue<PhysicalQuantity.Energy, EnergyUnit>,
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>,
    factory: (Decimal, WeightUnit) -> Value
) = byDividing(energy, specificEnergy, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("massFromForceAndAccelerationDefault")
fun <
    MassUnit : Weight,
    AccelerationUnit : Acceleration,
    ForceUnit : Force
    > MassUnit.mass(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>
) = mass(force, acceleration, ::DefaultScientificValue)

@JvmName("massFromForceAndAcceleration")
fun <
    MassUnit : Weight,
    AccelerationUnit : Acceleration,
    ForceUnit : Force,
    Value : ScientificValue<PhysicalQuantity.Weight, MassUnit>
    > MassUnit.mass(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>,
    factory: (Decimal, MassUnit) -> Value
) = byDividing(force, acceleration, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.HeatCapacity
import com.splendo.kaluga.scientific.unit.SpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("weightFromHeatCapacityAndSpecificHeatCapacityDefault")
fun <
    HeatCapacityUnit : HeatCapacity,
    WeightUnit : Weight,
    SpecificHeatCapacityUnit : SpecificHeatCapacity
    > WeightUnit.weight(
    heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, HeatCapacityUnit>,
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>
) = weight(heatCapacity, specificHeatCapacity, ::DefaultScientificValue)

@JvmName("weightFromHeatCapacityAndSpecificHeatCapacity")
fun <
    HeatCapacityUnit : HeatCapacity,
    WeightUnit : Weight,
    SpecificHeatCapacityUnit : SpecificHeatCapacity,
    Value : ScientificValue<PhysicalQuantity.Weight, WeightUnit>
    > WeightUnit.weight(
    heatCapacity: ScientificValue<PhysicalQuantity.HeatCapacity, HeatCapacityUnit>,
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>,
    factory: (Decimal, WeightUnit) -> Value
) = byDividing(heatCapacity, specificHeatCapacity, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("weightFromLinearMassDensityAndLengthDefault")
fun <
    WeightUnit : Weight,
    LengthUnit : Length,
    LinearMassDensityUnit : LinearMassDensity
    > WeightUnit.mass(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = mass(linearMassDensity, length, ::DefaultScientificValue)

@JvmName("weightFromLinearMassDensityAndLength")
fun <
    WeightUnit : Weight,
    LengthUnit : Length,
    LinearMassDensityUnit : LinearMassDensity,
    Value : ScientificValue<PhysicalQuantity.Weight, WeightUnit>
    > WeightUnit.mass(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>,
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>,
    factory: (Decimal, WeightUnit) -> Value
) = byMultiplying(linearMassDensity, length, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.MassFlowRate
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("weightFromMassFlowRateAndTimeDefault")
fun <
    WeightUnit : Weight,
    TimeUnit : Time,
    MassFlowRateUnit : MassFlowRate
    > WeightUnit.mass(
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, MassFlowRateUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = mass(massFlowRate, time, ::DefaultScientificValue)

@JvmName("weightFromMassFlowRateAndTime")
fun <
    WeightUnit : Weight,
    TimeUnit : Time,
    MassFlowRateUnit : MassFlowRate,
    Value : ScientificValue<PhysicalQuantity.Weight, WeightUnit>
    > WeightUnit.mass(
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, MassFlowRateUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, WeightUnit) -> Value
) = byMultiplying(massFlowRate, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("weightFromMolarMassAndAmountOfSubstanceDefault")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    WeightUnit : Weight,
    MolarMassUnit : MolarMass
    > WeightUnit.mass(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = mass(molarMass, amountOfSubstance, ::DefaultScientificValue)

@JvmName("weightFromMolarMassAndAmountOfSubstance")
fun <
    AmountOfSubstanceUnit : AmountOfSubstance,
    WeightUnit : Weight,
    MolarMassUnit : MolarMass,
    Value : ScientificValue<PhysicalQuantity.Weight, WeightUnit>
    > WeightUnit.mass(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>,
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>,
    factory: (Decimal, WeightUnit) -> Value
) = byMultiplying(molarMass, amountOfSubstance, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Momentum
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("massFromMomentumAndSpeedDefault")
fun <
    WeightUnit : Weight,
    SpeedUnit : Speed,
    MomentumUnit : Momentum
    > WeightUnit.mass(
    momentum: ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>,
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>
) = mass(momentum, speed, ::DefaultScientificValue)

@JvmName("massFromMomentumAndSpeed")
fun <
    WeightUnit : Weight,
    SpeedUnit : Speed,
    MomentumUnit : Momentum,
    Value : ScientificValue<PhysicalQuantity.Weight, WeightUnit>
    > WeightUnit.mass(
    momentum: ScientificValue<PhysicalQuantity.Momentum, MomentumUnit>,
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>,
    factory: (Decimal, WeightUnit) -> Value
) = byDividing(momentum, speed, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("weightFromVolumeAndSpecificVolumeDefault")
fun <
    VolumeUnit : Volume,
    WeightUnit : Weight,
    SpecificVolumeUnit : SpecificVolume
    > WeightUnit.weight(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = weight(volume, specificVolume, ::DefaultScientificValue)

@JvmName("weightFromVolumeAndSpecificVolume")
fun <
    VolumeUnit : Volume,
    WeightUnit : Weight,
    SpecificVolumeUnit : SpecificVolume,
    Value : ScientificValue<PhysicalQuantity.Weight, WeightUnit>
    > WeightUnit.weight(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>,
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>,
    factory: (Decimal, WeightUnit) -> Value
) = byDividing(volume, specificVolume, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Jolt
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.Yank
import kotlin.jvm.JvmName

@JvmName("massFromYankAndJoltDefault")
fun <
    WeightUnit : Weight,
    JoltUnit : Jolt,
    YankUnit : Yank
    > WeightUnit.mass(
    yank: ScientificValue<PhysicalQuantity.Yank, YankUnit>,
    jolt: ScientificValue<PhysicalQuantity.Jolt, JoltUnit>
) = mass(yank, jolt, ::DefaultScientificValue)

@JvmName("massFromYankAndJolt")
fun <
    WeightUnit : Weight,
    JoltUnit : Jolt,
    YankUnit : Yank,
    Value : ScientificValue<PhysicalQuantity.Weight, WeightUnit>
    > WeightUnit.mass(
    yank: ScientificValue<PhysicalQuantity.Yank, YankUnit>,
    jolt: ScientificValue<PhysicalQuantity.Jolt, JoltUnit>,
    factory: (Decimal, WeightUnit) -> Value
) = byDividing(yank, jolt, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.amountOfSubstance.amountOfSubstance
import com.splendo.kaluga.scientific.converter.molality.times
import com.splendo.kaluga.scientific.unit.Molality
import com.splendo.kaluga.scientific.unit.MolarMass
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("weightTimesMolality")
infix operator fun <MolalityUnit : Molality, WeightUnit : Weight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    molality: ScientificValue<PhysicalQuantity.Molality, MolalityUnit>
) = molality * this

@JvmName("weightDivMolarMass")
infix operator fun <WeightUnit : Weight, MolarMassUnit : MolarMass> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    molarMass: ScientificValue<PhysicalQuantity.MolarMass, MolarMassUnit>
) = molarMass.unit.per.amountOfSubstance(this, molarMass)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.area.area
import com.splendo.kaluga.scientific.unit.AreaDensity
import com.splendo.kaluga.scientific.unit.ImperialAreaDensity
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.MetricAreaDensity
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.UKImperialAreaDensity
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryAreaDensity
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("metricWeightDivMetricAreaDensity")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, MetricAreaDensity>
) = areaDensity.unit.per.area(this, areaDensity)

@JvmName("imperialWeightDivImperialAreaDensity")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>
) = areaDensity.unit.per.area(this, areaDensity)

@JvmName("imperialWeightDivUKImperialAreaDensity")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>
) = areaDensity.unit.per.area(this, areaDensity)

@JvmName("imperialWeightDivUSCustomaryAreaDensity")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>
) = areaDensity.unit.per.area(this, areaDensity)

@JvmName("ukImperialWeightDivImperialAreaDensity")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>
) = areaDensity.unit.per.area(this, areaDensity)

@JvmName("ukImperialWeightDivUKImperialAreaDensity")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, UKImperialAreaDensity>
) = areaDensity.unit.per.area(this, areaDensity)

@JvmName("usCustomaryWeightDivImperialAreaDensity")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, ImperialAreaDensity>
) = areaDensity.unit.per.area(this, areaDensity)

@JvmName("usCustomaryWeightDivUSCustomaryAreaDensity")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, USCustomaryAreaDensity>
) = areaDensity.unit.per.area(this, areaDensity)

@JvmName("weightDivAreaDensity")
infix operator fun <WeightUnit : Weight, AreaDensityUnit : AreaDensity> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    areaDensity: ScientificValue<PhysicalQuantity.AreaDensity, AreaDensityUnit>
) = SquareMeter.area(this, areaDensity)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.areaDensity.areaDensity
import com.splendo.kaluga.scientific.unit.Area
import com.splendo.kaluga.scientific.unit.ImperialArea
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricArea
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.SquareMeter
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricWeightDivMetricArea")
infix operator fun <WeightUnit : MetricWeight, AreaUnit : MetricArea> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit per area.unit).areaDensity(this, area)

@JvmName("imperialWeightDivImperialArea")
infix operator fun <WeightUnit : ImperialWeight, AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit per area.unit).areaDensity(this, area)

@JvmName("ukImperialWeightDivImperialArea")
infix operator fun <WeightUnit : UKImperialWeight, AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit per area.unit).areaDensity(this, area)

@JvmName("usCustomaryWeightDivImperialArea")
infix operator fun <WeightUnit : USCustomaryWeight, AreaUnit : ImperialArea> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (unit per area.unit).areaDensity(this, area)

@JvmName("weightDivArea")
infix operator fun <WeightUnit : Weight, AreaUnit : Area> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    area: ScientificValue<PhysicalQuantity.Area, AreaUnit>
) = (Kilogram per SquareMeter).areaDensity(this, area)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.density.density
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.ImperialVolume
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricVolume
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.UKImperialVolume
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryVolume
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Volume
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricWeightDivMetricVolume")
infix operator fun <WeightUnit : MetricWeight, VolumeUnit : MetricVolume> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = (unit per volume.unit).density(this, volume)

@JvmName("imperialWeightDivImperialVolume")
infix operator fun <WeightUnit : ImperialWeight, VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = (unit per volume.unit).density(this, volume)

@JvmName("imperialWeightDivUKImperialVolume")
infix operator fun <WeightUnit : ImperialWeight, VolumeUnit : UKImperialVolume> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = (unit per volume.unit).density(this, volume)

@JvmName("imperialWeightDivUSCustomaryVolume")
infix operator fun <WeightUnit : ImperialWeight, VolumeUnit : USCustomaryVolume> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = (unit per volume.unit).density(this, volume)

@JvmName("ukImperialWeightDivImperialVolume")
infix operator fun <WeightUnit : UKImperialWeight, VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = (unit per volume.unit).density(this, volume)

@JvmName("ukImperialWeightDivUKImperialVolume")
infix operator fun <WeightUnit : UKImperialWeight, VolumeUnit : UKImperialVolume> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = (unit per volume.unit).density(this, volume)

@JvmName("usCustomaryWeightDivImperialVolume")
infix operator fun <WeightUnit : USCustomaryWeight, VolumeUnit : ImperialVolume> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = (unit per volume.unit).density(this, volume)

@JvmName("usCustomaryWeightDivUSCustomaryVolume")
infix operator fun <WeightUnit : USCustomaryWeight, VolumeUnit : USCustomaryVolume> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = (unit per volume.unit).density(this, volume)

@JvmName("weightDivVolume")
infix operator fun <WeightUnit : Weight, VolumeUnit : Volume> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    volume: ScientificValue<PhysicalQuantity.Volume, VolumeUnit>
) = (Kilogram per CubicMeter).density(this, volume)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.ionizingRadiationAbsorbedDose.times
import com.splendo.kaluga.scientific.converter.ionizingRadiationEquivalentDose.times
import com.splendo.kaluga.scientific.converter.specificEnergy.times
import com.splendo.kaluga.scientific.unit.Gram
import com.splendo.kaluga.scientific.unit.ImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.IonizingRadiationAbsorbedDose
import com.splendo.kaluga.scientific.unit.IonizingRadiationEquivalentDose
import com.splendo.kaluga.scientific.unit.MeasurementSystem
import com.splendo.kaluga.scientific.unit.MetricMultipleUnit
import com.splendo.kaluga.scientific.unit.MetricSpecificEnergy
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.Rad
import com.splendo.kaluga.scientific.unit.RoentgenEquivalentMan
import com.splendo.kaluga.scientific.unit.SpecificEnergy
import com.splendo.kaluga.scientific.unit.UKImperialSpecificEnergy
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomarySpecificEnergy
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("gramTimesRad")
infix operator fun ScientificValue<PhysicalQuantity.Weight, Gram>.times(
    absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad>
) = absorbedDose * this

@JvmName("gramTimesRadMultiple")
infix operator fun <AbsorbedDoseUnit> ScientificValue<PhysicalQuantity.Weight, Gram>.times(
    absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>
) where AbsorbedDoseUnit : IonizingRadiationAbsorbedDose, AbsorbedDoseUnit : MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationAbsorbedDose, Rad> =
    absorbedDose * this

@JvmName("imperialWeightTimesAbsorbedDose")
infix operator fun <AbsorbedDoseUnit : IonizingRadiationAbsorbedDose, WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>
) = absorbedDose * this

@JvmName("ukImperialWeightTimesAbsorbedDose")
infix operator fun <AbsorbedDoseUnit : IonizingRadiationAbsorbedDose, WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>
) = absorbedDose * this

@JvmName("usCustomaryWeightTimesAbsorbedDose")
infix operator fun <AbsorbedDoseUnit : IonizingRadiationAbsorbedDose, WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>
) = absorbedDose * this

@JvmName("weightTimesAbsorbedDose")
infix operator fun <AbsorbedDoseUnit : IonizingRadiationAbsorbedDose, WeightUnit : Weight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    absorbedDose: ScientificValue<PhysicalQuantity.IonizingRadiationAbsorbedDose, AbsorbedDoseUnit>
) = absorbedDose * this

@JvmName("gramTimesRoentgenEquivalentMan")
infix operator fun ScientificValue<PhysicalQuantity.Weight, Gram>.times(
    equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan>
) = equivalentDose * this

@JvmName("gramTimesRoentgenEquivalentManMultiple")
infix operator fun <EquivalentDoseUnit> ScientificValue<PhysicalQuantity.Weight, Gram>.times(
    equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>
) where EquivalentDoseUnit : IonizingRadiationEquivalentDose, EquivalentDoseUnit : MetricMultipleUnit<MeasurementSystem.MetricAndImperial, PhysicalQuantity.IonizingRadiationEquivalentDose, RoentgenEquivalentMan> =
    equivalentDose * this

@JvmName("imperialWeightTimesEquivalentDose")
infix operator fun <EquivalentDoseUnit : IonizingRadiationEquivalentDose, WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>
) = equivalentDose * this

@JvmName("ukImperialWeightTimesEquivalentDose")
infix operator fun <EquivalentDoseUnit : IonizingRadiationEquivalentDose, WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>
) = equivalentDose * this

@JvmName("usCustomaryWeightTimesEquivalentDose")
infix operator fun <EquivalentDoseUnit : IonizingRadiationEquivalentDose, WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>
) = equivalentDose * this

@JvmName("weightTimesEquivalentDose")
infix operator fun <EquivalentDoseUnit : IonizingRadiationEquivalentDose, WeightUnit : Weight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    equivalentDose: ScientificValue<PhysicalQuantity.IonizingRadiationEquivalentDose, EquivalentDoseUnit>
) = equivalentDose * this

@JvmName("metricWeightTimesMetricSpecificEnergy")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, MetricSpecificEnergy>
) = specificEnergy * this

@JvmName("imperialWeightTimesImperialSpecificEnergy")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>
) = specificEnergy * this

@JvmName("ukImperialWeightTimesImperialSpecificEnergy")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>
) = specificEnergy * this

@JvmName("usCustomaryWeightTimesImperialSpecificEnergy")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, ImperialSpecificEnergy>
) = specificEnergy * this

@JvmName("imperialWeightTimesUKImperialSpecificEnergy")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, UKImperialSpecificEnergy>
) = specificEnergy * this

@JvmName("ukImperialWeightTimesUKImperialSpecificEnergy")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, UKImperialSpecificEnergy>
) = specificEnergy * this

@JvmName("imperialWeightTimesUSCustomarySpecificEnergy")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, USCustomarySpecificEnergy>
) = specificEnergy * this

@JvmName("usCustomaryWeightTimesUSCustomarySpecificEnergy")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, USCustomarySpecificEnergy>
) = specificEnergy * this

@JvmName("weightTimesSpecificEnergy")
infix operator fun <SpecificEnergyUnit : SpecificEnergy, WeightUnit : Weight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificEnergy: ScientificValue<PhysicalQuantity.SpecificEnergy, SpecificEnergyUnit>
) = specificEnergy * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.force.force
import com.splendo.kaluga.scientific.unit.Acceleration
import com.splendo.kaluga.scientific.unit.Dyne
import com.splendo.kaluga.scientific.unit.Grain
import com.splendo.kaluga.scientific.unit.GrainForce
import com.splendo.kaluga.scientific.unit.Gram
import com.splendo.kaluga.scientific.unit.ImperialAcceleration
import com.splendo.kaluga.scientific.unit.ImperialTon
import com.splendo.kaluga.scientific.unit.ImperialTonForce
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.MetricAcceleration
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.Newton
import com.splendo.kaluga.scientific.unit.Ounce
import com.splendo.kaluga.scientific.unit.OunceForce
import com.splendo.kaluga.scientific.unit.Pound
import com.splendo.kaluga.scientific.unit.PoundForce
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.UsTon
import com.splendo.kaluga.scientific.unit.UsTonForce
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.ukImperial
import com.splendo.kaluga.scientific.unit.usCustomary
import kotlin.jvm.JvmName

@JvmName("gramTimesAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.Weight, Gram>.times(acceleration: ScientificValue<PhysicalQuantity.Acceleration, MetricAcceleration>) =
    Dyne.force(this, acceleration)

@JvmName("metricWeightTimesMetricAcceleration")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, MetricAcceleration>
) = Newton.force(this, acceleration)

@JvmName("poundTimesAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.Weight, Pound>.times(acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>) =
    PoundForce.force(this, acceleration)

@JvmName("ounceTimesAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.Weight, Ounce>.times(acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>) =
    OunceForce.force(this, acceleration)

@JvmName("grainTimesAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.Weight, Grain>.times(acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>) =
    GrainForce.force(this, acceleration)

@JvmName("usTonTimesAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.Weight, UsTon>.times(acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>) =
    UsTonForce.force(this, acceleration)

@JvmName("imperialTonTimesAcceleration")
infix operator fun ScientificValue<PhysicalQuantity.Weight, ImperialTon>.times(acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>) =
    ImperialTonForce.force(this, acceleration)

@JvmName("imperialWeightTimesImperialAcceleration")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>
) = PoundForce.force(this, acceleration)

@JvmName("ukImperialWeightTimesImperialAcceleration")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>
) = PoundForce.ukImperial.force(this, acceleration)

@JvmName("usCustomaryWeightTimesImperialAcceleration")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, ImperialAcceleration>
) = PoundForce.usCustomary.force(this, acceleration)

@JvmName("weightTimesAcceleration")
infix operator fun <WeightUnit : Weight, AccelerationUnit : Acceleration> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    acceleration: ScientificValue<PhysicalQuantity.Acceleration, AccelerationUnit>
) = Newton.force(this, acceleration)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.specificHeatCapacity.times
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.MetricSpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.SpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomarySpecificHeatCapacity
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("metricWeightTimesMetricSpecificHeatCapacity")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, MetricSpecificHeatCapacity>
) = specificHeatCapacity * this

@JvmName("imperialWeightTimesUKImperialSpecificHeatCapacity")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, UKImperialSpecificHeatCapacity>
) = specificHeatCapacity * this

@JvmName("ukImperialWeightUKImperialSpecificHeatCapacity")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, UKImperialSpecificHeatCapacity>
) = specificHeatCapacity * this

@JvmName("imperialWeightTimesUSCustomarySpecificHeatCapacity")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, USCustomarySpecificHeatCapacity>
) = specificHeatCapacity * this

@JvmName("usCustomaryWeightTimesUSCustomarySpecificHeatCapacity")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, USCustomarySpecificHeatCapacity>
) = specificHeatCapacity * this

@JvmName("weightTimesSpecificHeatCapacity")
infix operator fun <SpecificHeatCapacityUnit : SpecificHeatCapacity, WeightUnit : Weight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificHeatCapacity: ScientificValue<PhysicalQuantity.SpecificHeatCapacity, SpecificHeatCapacityUnit>
) = specificHeatCapacity * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.length.length
import com.splendo.kaluga.scientific.unit.ImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.LinearMassDensity
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricLinearMassDensity
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.UKImperialLinearMassDensity
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryLinearMassDensity
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("metricWeightDivMetricLinearMassDensity")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, MetricLinearMassDensity>
) = linearMassDensity.unit.per.length(this, linearMassDensity)

@JvmName("imperialWeightDivImperialLinearMassDensity")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>
) = linearMassDensity.unit.per.length(this, linearMassDensity)

@JvmName("imperialWeightDivUKImperialLinearMassDensity")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, UKImperialLinearMassDensity>
) = linearMassDensity.unit.per.length(this, linearMassDensity)

@JvmName("imperialWeightDivUSCustomaryLinearMassDensity")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, USCustomaryLinearMassDensity>
) = linearMassDensity.unit.per.length(this, linearMassDensity)

@JvmName("ukImperialWeightDivImperialLinearMassDensity")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>
) = linearMassDensity.unit.per.length(this, linearMassDensity)

@JvmName("ukImperialWeightDivUKImperialLinearMassDensity")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, UKImperialLinearMassDensity>
) = linearMassDensity.unit.per.length(this, linearMassDensity)

@JvmName("usCustomaryWeightDivImperialLinearMassDensity")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, ImperialLinearMassDensity>
) = linearMassDensity.unit.per.length(this, linearMassDensity)

@JvmName("usCustomaryWeightDivUSCustomaryLinearMassDensity")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, USCustomaryLinearMassDensity>
) = linearMassDensity.unit.per.length(this, linearMassDensity)

@JvmName("weightDivLinearMassDensity")
infix operator fun <WeightUnit : Weight, LinearMassDensityUnit : LinearMassDensity> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    linearMassDensity: ScientificValue<PhysicalQuantity.LinearMassDensity, LinearMassDensityUnit>
) = Meter.length(this, linearMassDensity)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.linearMassDensity.linearMassDensity
import com.splendo.kaluga.scientific.unit.ImperialLength
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.Length
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricLength
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricWeightDivMetricLength")
infix operator fun <WeightUnit : MetricWeight, LengthUnit : MetricLength> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit per length.unit).linearMassDensity(this, length)

@JvmName("imperialWeightDivImperialLength")
infix operator fun <WeightUnit : ImperialWeight, LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit per length.unit).linearMassDensity(this, length)

@JvmName("ukImperialWeightDivImperialLength")
infix operator fun <WeightUnit : UKImperialWeight, LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit per length.unit).linearMassDensity(this, length)

@JvmName("usCustomaryWeightDivImperialLength")
infix operator fun <WeightUnit : USCustomaryWeight, LengthUnit : ImperialLength> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (unit per length.unit).linearMassDensity(this, length)

@JvmName("weightDivLength")
infix operator fun <WeightUnit : Weight, LengthUnit : Length> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    length: ScientificValue<PhysicalQuantity.Length, LengthUnit>
) = (Kilogram per Meter).linearMassDensity(this, length)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.massFlowRate.massFlowRate
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricWeightDivTime")
infix operator fun <WeightUnit : MetricWeight, TimeUnit : Time> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).massFlowRate(this, time)

@JvmName("imperialWeightDivTime")
infix operator fun <WeightUnit : ImperialWeight, TimeUnit : Time> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).massFlowRate(this, time)

@JvmName("ukImperialWeightDivTime")
infix operator fun <WeightUnit : UKImperialWeight, TimeUnit : Time> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).massFlowRate(this, time)

@JvmName("usCustomaryWeightDivTime")
infix operator fun <WeightUnit : USCustomaryWeight, TimeUnit : Time> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit per time.unit).massFlowRate(this, time)

@JvmName("weightDivTime")
infix operator fun <WeightUnit : Weight, TimeUnit : Time> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (Kilogram per time.unit).massFlowRate(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.molarMass.molarMass
import com.splendo.kaluga.scientific.unit.AmountOfSubstance
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricWeightDivAmountOfSubstance")
infix operator fun <WeightUnit : MetricWeight, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (unit per amountOfSubstance.unit).molarMass(this, amountOfSubstance)

@JvmName("imperialWeightDivAmountOfSubstance")
infix operator fun <WeightUnit : ImperialWeight, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (unit per amountOfSubstance.unit).molarMass(this, amountOfSubstance)

@JvmName("ukImperialWeightDivAmountOfSubstance")
infix operator fun <WeightUnit : UKImperialWeight, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (unit per amountOfSubstance.unit).molarMass(this, amountOfSubstance)

@JvmName("usCustomaryWeightDivAmountOfSubstance")
infix operator fun <WeightUnit : USCustomaryWeight, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (unit per amountOfSubstance.unit).molarMass(this, amountOfSubstance)

@JvmName("weightDivAmountOfSubstance")
infix operator fun <WeightUnit : Weight, AmountOfSubstanceUnit : AmountOfSubstance> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    amountOfSubstance: ScientificValue<PhysicalQuantity.AmountOfSubstance, AmountOfSubstanceUnit>
) = (Kilogram per amountOfSubstance.unit).molarMass(this, amountOfSubstance)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.momentum.momentum
import com.splendo.kaluga.scientific.unit.ImperialSpeed
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.Kilogram
import com.splendo.kaluga.scientific.unit.Meter
import com.splendo.kaluga.scientific.unit.MetricSpeed
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.Second
import com.splendo.kaluga.scientific.unit.Speed
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.per
import com.splendo.kaluga.scientific.unit.x
import kotlin.jvm.JvmName

@JvmName("metricWeightTimesMetricSpeed")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    speed: ScientificValue<PhysicalQuantity.Speed, MetricSpeed>
) = (unit x speed.unit).momentum(this, speed)

@JvmName("imperialWeightTimesImperialSpeed")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    speed: ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>
) = (unit x speed.unit).momentum(this, speed)

@JvmName("ukImperialWeightTimesImperialSpeed")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    speed: ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>
) = (unit x speed.unit).momentum(this, speed)

@JvmName("usCustomaryWeightTimesImperialSpeed")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    speed: ScientificValue<PhysicalQuantity.Speed, ImperialSpeed>
) = (unit x speed.unit).momentum(this, speed)

@JvmName("weightTimesSpeed")
infix operator fun <WeightUnit : Weight, SpeedUnit : Speed> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    speed: ScientificValue<PhysicalQuantity.Speed, SpeedUnit>
) = (Kilogram x (Meter per Second)).momentum(this, speed)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.time.time
import com.splendo.kaluga.scientific.unit.ImperialMassFlowRate
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.MassFlowRate
import com.splendo.kaluga.scientific.unit.MetricMassFlowRate
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.UKImperialMassFlowRate
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryMassFlowRate
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("metricWeightDivMetricMassFlowRate")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, MetricMassFlowRate>
) = massFlowRate.unit.per.time(this, massFlowRate)

@JvmName("imperialWeightDivImperialMassFlowRate")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, ImperialMassFlowRate>
) = massFlowRate.unit.per.time(this, massFlowRate)

@JvmName("ukImperialWeightDivImperialMassFlowRate")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, ImperialMassFlowRate>
) = massFlowRate.unit.per.time(this, massFlowRate)

@JvmName("ukImperialWeightDivUKImperialMassFlowRate")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, UKImperialMassFlowRate>
) = massFlowRate.unit.per.time(this, massFlowRate)

@JvmName("usCustomaryWeightDivImperialMassFlowRate")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, ImperialMassFlowRate>
) = massFlowRate.unit.per.time(this, massFlowRate)

@JvmName("usCustomaryWeightDivUSCustomaryMassFlowRate")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, USCustomaryMassFlowRate>
) = massFlowRate.unit.per.time(this, massFlowRate)

@JvmName("weightDivMassFlowRate")
infix operator fun <WeightUnit : Weight, MassFlowRateUnit : MassFlowRate> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    massFlowRate: ScientificValue<PhysicalQuantity.MassFlowRate, MassFlowRateUnit>
) = massFlowRate.unit.per.time(this, massFlowRate)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.specificVolume.times
import com.splendo.kaluga.scientific.converter.volume.volume
import com.splendo.kaluga.scientific.unit.CubicMeter
import com.splendo.kaluga.scientific.unit.Density
import com.splendo.kaluga.scientific.unit.ImperialDensity
import com.splendo.kaluga.scientific.unit.ImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.MetricDensity
import com.splendo.kaluga.scientific.unit.MetricSpecificVolume
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.SpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialDensity
import com.splendo.kaluga.scientific.unit.UKImperialSpecificVolume
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryDensity
import com.splendo.kaluga.scientific.unit.USCustomarySpecificVolume
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import kotlin.jvm.JvmName

@JvmName("metricWeightDivMetricDensity")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, MetricDensity>
) = density.unit.per.volume(this, density)

@JvmName("imperialWeightDivImperialDensity")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>
) = density.unit.per.volume(this, density)

@JvmName("imperialWeightDivUKImperialDensity")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, UKImperialDensity>
) = density.unit.per.volume(this, density)

@JvmName("imperialWeightDivUSCustomaryDensity")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>
) = density.unit.per.volume(this, density)

@JvmName("ukImperialWeightDivImperialDensity")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>
) = density.unit.per.volume(this, density)

@JvmName("ukImperialWeightDivUKImperialDensity")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, UKImperialDensity>
) = density.unit.per.volume(this, density)

@JvmName("usCustomaryWeightDivImperialDensity")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, ImperialDensity>
) = density.unit.per.volume(this, density)

@JvmName("usCustomaryWeightDivUSCustomaryDensity")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, USCustomaryDensity>
) = density.unit.per.volume(this, density)

@JvmName("weightDivDensity")
infix operator fun <WeightUnit : Weight, DensityUnit : Density> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.div(
    density: ScientificValue<PhysicalQuantity.Density, DensityUnit>
) = CubicMeter.volume(this, density)

@JvmName("metricWeightTimesMetricSpecificVolume")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, MetricSpecificVolume>
) = specificVolume * this

@JvmName("imperialWeightTimesImperialSpecificVolume")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = specificVolume * this

@JvmName("imperialWeightTimesUKImperialSpecificVolume")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>
) = specificVolume * this

@JvmName("imperialWeightTimesUSCustomarySpecificVolume")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>
) = specificVolume * this

@JvmName("ukImperialWeightTimesImperialSpecificVolume")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = specificVolume * this

@JvmName("ukImperialWeightTimesUKImperialSpecificVolume")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, UKImperialSpecificVolume>
) = specificVolume * this

@JvmName("usCustomaryWeightTimesImperialSpecificVolume")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, ImperialSpecificVolume>
) = specificVolume * this

@JvmName("usCustomaryWeightTimesUSCustomarySpecificVolume")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, USCustomarySpecificVolume>
) = specificVolume * this

@JvmName("weightTimesSpecificVolume")
infix operator fun <SpecificVolumeUnit : SpecificVolume, WeightUnit : Weight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    specificVolume: ScientificValue<PhysicalQuantity.SpecificVolume, SpecificVolumeUnit>
) = specificVolume * this

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.weight

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.yank.yank
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.ImperialJolt
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.Jolt
import com.splendo.kaluga.scientific.unit.MetricJolt
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricWeightTimesMetricJolt")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    jolt: ScientificValue<PhysicalQuantity.Jolt, MetricJolt>
) = ((this * 1.0(jolt.unit.acceleration)).unit per jolt.unit.per).yank(this, jolt)

@JvmName("imperialWeightTimesImperialJolt")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    jolt: ScientificValue<PhysicalQuantity.Jolt, ImperialJolt>
) = ((this * 1.0(jolt.unit.acceleration)).unit per jolt.unit.per).yank(this, jolt)

@JvmName("ukImperialWeightTimesImperialJolt")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    jolt: ScientificValue<PhysicalQuantity.Jolt, ImperialJolt>
) = ((this * 1.0(jolt.unit.acceleration)).unit per jolt.unit.per).yank(this, jolt)

@JvmName("usCustomaryWeightTimesImperialJolt")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    jolt: ScientificValue<PhysicalQuantity.Jolt, ImperialJolt>
) = ((this * 1.0(jolt.unit.acceleration)).unit per jolt.unit.per).yank(this, jolt)

@JvmName("weightTimesJolt")
infix operator fun <WeightUnit : Weight, JoltUnit : Jolt> ScientificValue<PhysicalQuantity.Weight, WeightUnit>.times(
    jolt: ScientificValue<PhysicalQuantity.Jolt, JoltUnit>
) = ((this * 1.0(jolt.unit.acceleration)).unit per jolt.unit.per).yank(this, jolt)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.yank

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byDividing
import com.splendo.kaluga.scientific.unit.Force
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.Yank
import kotlin.jvm.JvmName

@JvmName("yankFromForceAndTimeDefault")
fun <
    ForceUnit : Force,
    TimeUnit : Time,
    YankUnit : Yank
    > YankUnit.yank(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = yank(force, time, ::DefaultScientificValue)

@JvmName("yankFromForceAndTime")
fun <
    ForceUnit : Force,
    TimeUnit : Time,
    YankUnit : Yank,
    Value : ScientificValue<PhysicalQuantity.Yank, YankUnit>
    > YankUnit.yank(
    force: ScientificValue<PhysicalQuantity.Force, ForceUnit>,
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>,
    factory: (Decimal, YankUnit) -> Value
) = byDividing(force, time, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.yank

import com.splendo.kaluga.base.utils.Decimal
import com.splendo.kaluga.scientific.DefaultScientificValue
import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.byMultiplying
import com.splendo.kaluga.scientific.unit.Jolt
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.Yank
import kotlin.jvm.JvmName

@JvmName("yankFromMassAndJoltDefault")
fun <
    WeightUnit : Weight,
    JoltUnit : Jolt,
    YankUnit : Yank
    > YankUnit.yank(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    jolt: ScientificValue<PhysicalQuantity.Jolt, JoltUnit>
) = yank(weight, jolt, ::DefaultScientificValue)

@JvmName("yankFromMassAndJolt")
fun <
    WeightUnit : Weight,
    JoltUnit : Jolt,
    YankUnit : Yank,
    Value : ScientificValue<PhysicalQuantity.Yank, YankUnit>
    > YankUnit.yank(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>,
    jolt: ScientificValue<PhysicalQuantity.Jolt, JoltUnit>,
    factory: (Decimal, YankUnit) -> Value
) = byMultiplying(weight, jolt, factory)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.yank

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.force.force
import com.splendo.kaluga.scientific.unit.ImperialYank
import com.splendo.kaluga.scientific.unit.MetricYank
import com.splendo.kaluga.scientific.unit.Time
import com.splendo.kaluga.scientific.unit.UKImperialYank
import com.splendo.kaluga.scientific.unit.USCustomaryYank
import com.splendo.kaluga.scientific.unit.Yank
import kotlin.jvm.JvmName

@JvmName("metricYankTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Yank, MetricYank>.times(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    (unit.force).force(this, time)

@JvmName("imperialYankTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Yank, ImperialYank>.times(time: ScientificValue<PhysicalQuantity.Time, TimeUnit>) =
    (unit.force).force(this, time)

@JvmName("ukImperialYankTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Yank, UKImperialYank>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit.force).force(this, time)

@JvmName("usCustomaryYankTimesTime")
infix operator fun <TimeUnit : Time> ScientificValue<PhysicalQuantity.Yank, USCustomaryYank>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit.force).force(this, time)

@JvmName("yankTimesTime")
infix operator fun <YankUnit : Yank, TimeUnit : Time> ScientificValue<PhysicalQuantity.Yank, YankUnit>.times(
    time: ScientificValue<PhysicalQuantity.Time, TimeUnit>
) = (unit.force).force(this, time)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.yank

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.force.div
import com.splendo.kaluga.scientific.converter.jolt.jolt
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.ImperialWeight
import com.splendo.kaluga.scientific.unit.ImperialYank
import com.splendo.kaluga.scientific.unit.MetricWeight
import com.splendo.kaluga.scientific.unit.MetricYank
import com.splendo.kaluga.scientific.unit.UKImperialWeight
import com.splendo.kaluga.scientific.unit.UKImperialYank
import com.splendo.kaluga.scientific.unit.USCustomaryWeight
import com.splendo.kaluga.scientific.unit.USCustomaryYank
import com.splendo.kaluga.scientific.unit.Weight
import com.splendo.kaluga.scientific.unit.Yank
import com.splendo.kaluga.scientific.unit.per
import kotlin.jvm.JvmName

@JvmName("metricYankDivMetricWeight")
infix operator fun <WeightUnit : MetricWeight> ScientificValue<PhysicalQuantity.Yank, MetricYank>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = ((1.0(unit.force) / weight).unit per unit.per).jolt(this, weight)

@JvmName("imperialYankDivImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Yank, ImperialYank>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = ((1.0(unit.force) / weight).unit per unit.per).jolt(this, weight)

@JvmName("imperialYankDivUKImperialWeight")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Yank, ImperialYank>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = ((1.0(unit.force) / weight).unit per unit.per).jolt(this, weight)

@JvmName("imperialYankDivUSCustomaryWeight")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Yank, ImperialYank>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = ((1.0(unit.force) / weight).unit per unit.per).jolt(this, weight)

@JvmName("ukImperialYankDivImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Yank, UKImperialYank>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = ((1.0(unit.force) / weight).unit per unit.per).jolt(this, weight)

@JvmName("ukImperialYankDivUKImperialWeight")
infix operator fun <WeightUnit : UKImperialWeight> ScientificValue<PhysicalQuantity.Yank, UKImperialYank>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = ((1.0(unit.force) / weight).unit per unit.per).jolt(this, weight)

@JvmName("usCustomaryYankDivImperialWeight")
infix operator fun <WeightUnit : ImperialWeight> ScientificValue<PhysicalQuantity.Yank, USCustomaryYank>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = ((1.0(unit.force) / weight).unit per unit.per).jolt(this, weight)

@JvmName("usCustomaryYankDivUSCustomaryWeight")
infix operator fun <WeightUnit : USCustomaryWeight> ScientificValue<PhysicalQuantity.Yank, USCustomaryYank>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = ((1.0(unit.force) / weight).unit per unit.per).jolt(this, weight)

@JvmName("yankDivWeight")
infix operator fun <YankUnit : Yank, WeightUnit : Weight> ScientificValue<PhysicalQuantity.Yank, YankUnit>.div(
    weight: ScientificValue<PhysicalQuantity.Weight, WeightUnit>
) = ((1.0(unit.force) / weight).unit per unit.per).jolt(this, weight)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.scientific.converter.yank

import com.splendo.kaluga.scientific.PhysicalQuantity
import com.splendo.kaluga.scientific.ScientificValue
import com.splendo.kaluga.scientific.converter.force.div
import com.splendo.kaluga.scientific.converter.weight.mass
import com.splendo.kaluga.scientific.invoke
import com.splendo.kaluga.scientific.unit.ImperialJolt
import com.splendo.kaluga.scientific.unit.ImperialYank
import com.splendo.kaluga.scientific.unit.Jolt
import com.splendo.kaluga.scientific.unit.MetricJolt
import com.splendo.kaluga.scientific.unit.MetricYank
import com.splendo.kaluga.scientific.unit.UKImperialYank
import com.splendo.kaluga.scientific.unit.USCustomaryYank
import com.splendo.kaluga.scientific.unit.Yank
import kotlin.jvm.JvmName

@JvmName("metricYankDivMetricJolt")
infix operator fun ScientificValue<PhysicalQuantity.Yank, MetricYank>.div(jolt: ScientificValue<PhysicalQuantity.Jolt, MetricJolt>) =
    (1.0(unit.force) / 1.0(jolt.unit.acceleration)).unit.mass(this, jolt)

@JvmName("imperialYankDivImperialJolt")
infix operator fun ScientificValue<PhysicalQuantity.Yank, ImperialYank>.div(jolt: ScientificValue<PhysicalQuantity.Jolt, ImperialJolt>) =
    (1.0(unit.force) / 1.0(jolt.unit.acceleration)).unit.mass(this, jolt)

@JvmName("ukImperialYankDivImperialJolt")
infix operator fun ScientificValue<PhysicalQuantity.Yank, UKImperialYank>.div(jolt: ScientificValue<PhysicalQuantity.Jolt, ImperialJolt>) =
    (1.0(unit.force) / 1.0(jolt.unit.acceleration)).unit.mass(this, jolt)

@JvmName("usCustomaryYankDivImperialJolt")
infix operator fun ScientificValue<PhysicalQuantity.Yank, USCustomaryYank>.div(jolt: ScientificValue<PhysicalQuantity.Jolt, ImperialJolt>) =
    (1.0(unit.force) / 1.0(jolt.unit.acceleration)).unit.mass(this, jolt)

@JvmName("yankDivJolt")
infix operator fun <YankUnit : Yank, JoltUnit : Jolt> ScientificValue<PhysicalQuantity.Yank, YankUnit>.div(
    jolt: ScientificValue<PhysicalQuantity.Jolt, JoltUnit>
) = (1.0(unit.force) / 1.0(jolt.unit.acceleration)).unit.mass(this, jolt)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.android

import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.IntentSender
import android.content.ServiceConnection
import android.content.SharedPreferences
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.res.AssetManager
import android.content.res.Configuration
import android.content.res.Resources
import android.database.DatabaseErrorHandler
import android.database.sqlite.SQLiteDatabase
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.UserHandle
import android.view.Display
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.InputStream

@Suppress("RedundantNullableReturnType")
@SuppressLint("MissingPermission")
class MockContext : Context() {
    override fun getAssets(): AssetManager = error("not implemented")

    override fun getResources(): Resources = error("not implemented")

    override fun getPackageManager(): PackageManager = error("not implemented")

    override fun getContentResolver(): ContentResolver = error("not implemented")

    override fun getMainLooper(): Looper = error("not implemented")

    override fun getApplicationContext(): Context = error("not implemented")

    override fun setTheme(resid: Int) = error("not implemented")

    override fun getTheme(): Resources.Theme = error("not implemented")

    override fun getClassLoader(): ClassLoader = error("not implemented")

    override fun getPackageName(): String = error("not implemented")

    override fun getApplicationInfo(): ApplicationInfo = error("not implemented")

    override fun getPackageResourcePath(): String = error("not implemented")

    override fun getPackageCodePath(): String = error("not implemented")

    override fun getSharedPreferences(name: String?, mode: Int): SharedPreferences = error("not implemented")

    override fun moveSharedPreferencesFrom(sourceContext: Context?, name: String?): Boolean = error("not implemented")

    override fun deleteSharedPreferences(name: String?): Boolean = error("not implemented")

    override fun openFileInput(name: String?): FileInputStream = error("not implemented")

    override fun openFileOutput(name: String?, mode: Int): FileOutputStream = error("not implemented")

    override fun deleteFile(name: String?): Boolean = error("not implemented")

    override fun getFileStreamPath(name: String?): File = error("not implemented")

    override fun getDataDir(): File = error("not implemented")

    override fun getFilesDir(): File = error("not implemented")

    override fun getNoBackupFilesDir(): File = error("not implemented")

    override fun getExternalFilesDir(type: String?): File? = error("not implemented")

    override fun getExternalFilesDirs(type: String?): Array<File> = error("not implemented")

    override fun getObbDir(): File = error("not implemented")

    override fun getObbDirs(): Array<File> = error("not implemented")

    override fun getCacheDir(): File = error("not implemented")

    override fun getCodeCacheDir(): File = error("not implemented")

    override fun getExternalCacheDir(): File? = error("not implemented")

    override fun getExternalCacheDirs(): Array<File> = error("not implemented")

    override fun getExternalMediaDirs(): Array<File> = error("not implemented")

    override fun fileList(): Array<String> = error("not implemented")

    override fun getDir(name: String?, mode: Int): File = error("not implemented")

    override fun openOrCreateDatabase(
        name: String?,
        mode: Int,
        factory: SQLiteDatabase.CursorFactory?
    ): SQLiteDatabase = error("not implemented")

    override fun openOrCreateDatabase(
        name: String?,
        mode: Int,
        factory: SQLiteDatabase.CursorFactory?,
        errorHandler: DatabaseErrorHandler?
    ): SQLiteDatabase = error("not implemented")

    override fun moveDatabaseFrom(sourceContext: Context?, name: String?): Boolean = error("not implemented")

    override fun deleteDatabase(name: String?): Boolean = error("not implemented")

    override fun getDatabasePath(name: String?): File = error("not implemented")

    override fun databaseList(): Array<String> = error("not implemented")

    override fun getWallpaper(): Drawable = error("not implemented")

    override fun peekWallpaper(): Drawable = error("not implemented")

    override fun getWallpaperDesiredMinimumWidth(): Int = error("not implemented")

    override fun getWallpaperDesiredMinimumHeight(): Int = error("not implemented")

    override fun setWallpaper(bitmap: Bitmap?) = error("not implemented")

    override fun setWallpaper(data: InputStream?) = error("not implemented")

    override fun clearWallpaper() = error("not implemented")

    override fun startActivity(intent: Intent?) = error("not implemented")

    override fun startActivity(intent: Intent?, options: Bundle?) = error("not implemented")

    override fun startActivities(intents: Array<out Intent>?) = error("not implemented")

    override fun startActivities(intents: Array<out Intent>?, options: Bundle?) = error("not implemented")

    override fun startIntentSender(
        intent: IntentSender?,
        fillInIntent: Intent?,
        flagsMask: Int,
        flagsValues: Int,
        extraFlags: Int
    ) = error("not implemented")

    override fun startIntentSender(
        intent: IntentSender?,
        fillInIntent: Intent?,
        flagsMask: Int,
        flagsValues: Int,
        extraFlags: Int,
        options: Bundle?
    ) = error("not implemented")

    override fun sendBroadcast(intent: Intent?) = error("not implemented")

    override fun sendBroadcast(intent: Intent?, receiverPermission: String?) = error("not implemented")

    override fun sendOrderedBroadcast(intent: Intent?, receiverPermission: String?) = error("not implemented")

    override fun sendOrderedBroadcast(
        intent: Intent,
        receiverPermission: String?,
        resultReceiver: BroadcastReceiver?,
        scheduler: Handler?,
        initialCode: Int,
        initialData: String?,
        initialExtras: Bundle?
    ) = error("not implemented")

    override fun sendBroadcastAsUser(intent: Intent?, user: UserHandle?) = error("not implemented")

    override fun sendBroadcastAsUser(
        intent: Intent?,
        user: UserHandle?,
        receiverPermission: String?
    ) = error("not implemented")

    override fun sendOrderedBroadcastAsUser(
        intent: Intent?,
        user: UserHandle?,
        receiverPermission: String?,
        resultReceiver: BroadcastReceiver?,
        scheduler: Handler?,
        initialCode: Int,
        initialData: String?,
        initialExtras: Bundle?
    ) = error("not implemented")

    override fun sendStickyBroadcast(intent: Intent?) = error("not implemented")

    override fun sendStickyOrderedBroadcast(
        intent: Intent?,
        resultReceiver: BroadcastReceiver?,
        scheduler: Handler?,
        initialCode: Int,
        initialData: String?,
        initialExtras: Bundle?
    ) = error("not implemented")

    override fun removeStickyBroadcast(intent: Intent?) = error("not implemented")

    override fun sendStickyBroadcastAsUser(intent: Intent?, user: UserHandle?) = error("not implemented")

    override fun sendStickyOrderedBroadcastAsUser(
        intent: Intent?,
        user: UserHandle?,
        resultReceiver: BroadcastReceiver?,
        scheduler: Handler?,
        initialCode: Int,
        initialData: String?,
        initialExtras: Bundle?
    ) = error("not implemented")

    override fun removeStickyBroadcastAsUser(intent: Intent?, user: UserHandle?) = error("not implemented")

    override fun registerReceiver(receiver: BroadcastReceiver?, filter: IntentFilter?): Intent? = error("not implemented")

    override fun registerReceiver(
        receiver: BroadcastReceiver?,
        filter: IntentFilter?,
        flags: Int
    ): Intent? = error("not implemented")

    override fun registerReceiver(
        receiver: BroadcastReceiver?,
        filter: IntentFilter?,
        broadcastPermission: String?,
        scheduler: Handler?
    ): Intent? = error("not implemented")

    override fun registerReceiver(
        receiver: BroadcastReceiver?,
        filter: IntentFilter?,
        broadcastPermission: String?,
        scheduler: Handler?,
        flags: Int
    ): Intent? = error("not implemented")

    override fun unregisterReceiver(receiver: BroadcastReceiver?) = error("not implemented")

    override fun startService(service: Intent?): ComponentName? = error("not implemented")

    override fun startForegroundService(service: Intent?): ComponentName? = error("not implemented")

    override fun stopService(service: Intent?): Boolean = error("not implemented")

    override fun bindService(service: Intent?, conn: ServiceConnection, flags: Int): Boolean = error("not implemented")

    override fun unbindService(conn: ServiceConnection) = error("not implemented")

    override fun startInstrumentation(
        className: ComponentName,
        profileFile: String?,
        arguments: Bundle?
    ): Boolean = error("not implemented")

    override fun getSystemService(name: String): Any = error("not implemented")

    override fun getSystemServiceName(serviceClass: Class<*>): String? = error("not implemented")

    override fun checkPermission(permission: String, pid: Int, uid: Int): Int = error("not implemented")

    override fun checkCallingPermission(permission: String): Int = error("not implemented")

    override fun checkCallingOrSelfPermission(permission: String): Int = error("not implemented")

    override fun checkSelfPermission(permission: String): Int = error("not implemented")

    override fun enforcePermission(permission: String, pid: Int, uid: Int, message: String?) = error("not implemented")

    override fun enforceCallingPermission(permission: String, message: String?) = error("not implemented")

    override fun enforceCallingOrSelfPermission(permission: String, message: String?) = error("not implemented")

    override fun grantUriPermission(toPackage: String?, uri: Uri?, modeFlags: Int) = error("not implemented")

    override fun revokeUriPermission(uri: Uri?, modeFlags: Int) = error("not implemented")

    override fun revokeUriPermission(toPackage: String?, uri: Uri?, modeFlags: Int) = error("not implemented")

    override fun checkUriPermission(uri: Uri?, pid: Int, uid: Int, modeFlags: Int): Int = error("not implemented")

    override fun checkUriPermission(
        uri: Uri?,
        readPermission: String?,
        writePermission: String?,
        pid: Int,
        uid: Int,
        modeFlags: Int
    ): Int = error("not implemented")

    override fun checkCallingUriPermission(uri: Uri?, modeFlags: Int): Int = error("not implemented")

    override fun checkCallingOrSelfUriPermission(uri: Uri?, modeFlags: Int): Int = error("not implemented")

    override fun enforceUriPermission(
        uri: Uri?,
        pid: Int,
        uid: Int,
        modeFlags: Int,
        message: String?
    ) = error("not implemented")

    override fun enforceUriPermission(
        uri: Uri?,
        readPermission: String?,
        writePermission: String?,
        pid: Int,
        uid: Int,
        modeFlags: Int,
        message: String?
    ) = error("not implemented")

    override fun enforceCallingUriPermission(uri: Uri?, modeFlags: Int, message: String?) = error("not implemented")

    override fun enforceCallingOrSelfUriPermission(uri: Uri?, modeFlags: Int, message: String?) = error("not implemented")

    override fun createPackageContext(packageName: String?, flags: Int): Context = error("not implemented")

    override fun createContextForSplit(splitName: String?): Context = error("not implemented")

    override fun createConfigurationContext(overrideConfiguration: Configuration): Context = error("not implemented")

    override fun createDisplayContext(display: Display): Context = error("not implemented")

    override fun createDeviceProtectedStorageContext(): Context = error("not implemented")

    override fun isDeviceProtectedStorage(): Boolean = error("not implemented")
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.architecture

import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable

class MockLifecycleSubscriber : LifecycleSubscribable {
    override var manager: LifecycleSubscribable.LifecycleManager? = null

    override fun subscribe(manager: LifecycleSubscribable.LifecycleManager) {
        this.manager = manager
    }

    override fun unsubscribe() {
        manager = null
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import com.splendo.kaluga.base.utils.EmptyCompletableDeferred
import com.splendo.kaluga.base.utils.complete
import com.splendo.kaluga.bluetooth.CharacteristicWrapper
import com.splendo.kaluga.bluetooth.DescriptorWrapper
import com.splendo.kaluga.bluetooth.device.BluetoothGattWrapper
import kotlinx.coroutines.CompletableDeferred

class MockBluetoothGattWrapper : BluetoothGattWrapper {

    val connectCompleted = EmptyCompletableDeferred()
    val discoverServicesCompleted = EmptyCompletableDeferred()
    val disconnectCompleted = EmptyCompletableDeferred()
    val closeCompleted = EmptyCompletableDeferred()
    val readRemoteRssiCompleted = EmptyCompletableDeferred()
    val readCharacteristicCompleted = CompletableDeferred<CharacteristicWrapper>()
    val readDescriptorCompleted = CompletableDeferred<DescriptorWrapper>()
    val writeCharacteristicCompleted = CompletableDeferred<CharacteristicWrapper>()
    val writeDescriptorCompleted = CompletableDeferred<DescriptorWrapper>()
    val setCharacteristicNotificationCompleted = CompletableDeferred<Pair<CharacteristicWrapper, Boolean>>()

    override fun connect(): Boolean {
        connectCompleted.complete()
        return true
    }

    override fun discoverServices(): Boolean {
        discoverServicesCompleted.complete()
        return true
    }

    override fun disconnect() {
        disconnectCompleted.complete()
    }

    override fun close() {
        closeCompleted.complete()
    }

    override fun readRemoteRssi(): Boolean {
        readRemoteRssiCompleted.complete()
        return true
    }

    override fun readCharacteristic(wrapper: CharacteristicWrapper): Boolean {
        readCharacteristicCompleted.complete(wrapper)
        return true
    }

    override fun readDescriptor(wrapper: DescriptorWrapper): Boolean {
        readDescriptorCompleted.complete(wrapper)
        return true
    }

    override fun writeCharacteristic(wrapper: CharacteristicWrapper): Boolean {
        writeCharacteristicCompleted.complete(wrapper)
        return true
    }

    override fun writeDescriptor(wrapper: DescriptorWrapper): Boolean {
        writeDescriptorCompleted.complete(wrapper)
        return true
    }

    override fun setCharacteristicNotification(wrapper: CharacteristicWrapper, enable: Boolean): Boolean {
        setCharacteristicNotificationCompleted.complete(Pair(wrapper, enable))
        return true
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import com.splendo.kaluga.bluetooth.DescriptorWrapper
import com.splendo.kaluga.bluetooth.ServiceWrapper
import java.util.UUID

class AndroidMockCharacteristicWrapper(
    override val uuid: UUID = UUID.randomUUID(),
    descriptorUuids: List<UUID> = emptyList(),
    override val service: ServiceWrapper
) : MockCharacteristicWrapper {

    override var value: ByteArray? = null
    override fun updateValue(value: ByteArray?) {
        this.value = value
    }

    override val descriptors: List<DescriptorWrapper> = descriptorUuids.map { AndroidMockDescriptorWrapper(it, this) }
    override val permissions: Int
        get() = 0
    override val properties: Int
        get() = 0
    override var writeType = 0

    override fun setValue(newValue: String): Boolean {
        value = newValue.encodeToByteArray()
        return true
    }

    override fun setValue(newValue: ByteArray?): Boolean {
        value = newValue
        return true
    }

    override fun setValue(mantissa: Int, exponent: Int, formatType: Int, offset: Int): Boolean {
        return true
    }

    override fun getDescriptor(uuid: UUID): DescriptorWrapper? {
        return descriptors.firstOrNull { it.uuid == uuid }
    }

    override fun floatValue(formatType: Int, offset: Int): Float {
        return 0.0f
    }

    override fun intValue(formatType: Int, offset: Int): Int {
        return 0
    }

    override fun updateMockValue(value: ByteArray?) = updateValue(value)
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import com.splendo.kaluga.bluetooth.CharacteristicWrapper
import java.util.UUID

class AndroidMockDescriptorWrapper(override val uuid: UUID = UUID.randomUUID(), override val characteristic: CharacteristicWrapper) : MockDescriptorWrapper {
    override fun updateMockValue(value: ByteArray?) = updateValue(value)

    override var value: ByteArray? = null
    override val permissions: Int = 0

    override fun updateValue(value: ByteArray?) {
        setValue(value)
    }
    override fun setValue(newValue: ByteArray?): Boolean {
        value = newValue
        return true
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGattCallback
import android.content.Context
import com.splendo.kaluga.base.utils.EmptyCompletableDeferred
import com.splendo.kaluga.base.utils.complete
import com.splendo.kaluga.bluetooth.device.BluetoothGattWrapper
import com.splendo.kaluga.bluetooth.device.DeviceWrapper
import com.splendo.kaluga.bluetooth.device.Identifier
import kotlinx.coroutines.CompletableDeferred

class MockDeviceWrapper(override val name: String?, override val identifier: Identifier, override val bondState: Int) : DeviceWrapper {

    val connectGattCompleted = CompletableDeferred<BluetoothGattCallback>()
    val removeBondCompleted = EmptyCompletableDeferred()

    override fun connectGatt(context: Context, autoConnect: Boolean, callback: BluetoothGattCallback): BluetoothGattWrapper {
        connectGattCompleted.complete(callback)
        return MockBluetoothGattWrapper()
    }

    override fun removeBond() {
        removeBondCompleted.complete()
    }

    override val device: BluetoothDevice
        get() = error("This is a mock device")
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattService
import com.splendo.kaluga.bluetooth.CharacteristicWrapper
import com.splendo.kaluga.bluetooth.DefaultCharacteristicWrapper
import com.splendo.kaluga.bluetooth.DefaultGattServiceWrapper
import com.splendo.kaluga.bluetooth.ServiceWrapper
import java.util.UUID

class MockServiceWrapper(override val uuid: UUID = UUID.randomUUID(), characteristicUuids: List<Pair<UUID, List<UUID>>> = emptyList()) : ServiceWrapper {

    override val type: Int = 0
    override val instanceId: Int = 0
    private val mutableCharacteristics = mutableListOf<CharacteristicWrapper>(*characteristicUuids.map { AndroidMockCharacteristicWrapper(it.first, it.second, this) }.toTypedArray())
    override val characteristics: List<CharacteristicWrapper>
        get() = mutableCharacteristics
    private val mutableIncludedServices = mutableListOf<ServiceWrapper>()
    override val includedServices: List<ServiceWrapper>
        get() = mutableIncludedServices

    override fun getCharacteristic(uuid: UUID): CharacteristicWrapper? {
        return characteristics.firstOrNull { it.uuid == uuid }
    }

    override fun addCharacteristic(characteristic: BluetoothGattCharacteristic): Boolean {
        mutableCharacteristics.add(DefaultCharacteristicWrapper(characteristic))
        return true
    }

    override fun addService(service: BluetoothGattService): Boolean {
        mutableIncludedServices.add(DefaultGattServiceWrapper(service))
        return true
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import android.bluetooth.BluetoothDevice
import com.splendo.kaluga.bluetooth.ServiceWrapper
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo
import com.splendo.kaluga.bluetooth.device.DeviceWrapper

const val deviceName = "name"
const val address = ""
const val bondState = BluetoothDevice.BOND_NONE

actual fun createDeviceWrapper(
    deviceName: String?
): DeviceWrapper = MockDeviceWrapper(deviceName, address, bondState)

actual fun createServiceWrapper(
    stateRepo: DeviceStateFlowRepo,
    uuid: UUID,
    characteristics: List<Pair<java.util.UUID, List<java.util.UUID>>>
): ServiceWrapper =
    MockServiceWrapper(
        uuid,
        characteristics
    )

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.permissions

import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.test.mock.android.MockContext

actual val mockPermissionContext = PermissionContext(MockContext())

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.resources

import android.graphics.drawable.ColorDrawable
import com.splendo.kaluga.resources.Color
import com.splendo.kaluga.resources.Font
import com.splendo.kaluga.resources.Image
import org.mockito.Mockito

actual fun mockColor(): Color = 0
actual fun mockImage(): Image = Image(ColorDrawable(0))
actual fun mockFont(): Font = Font.DEFAULT ?: Mockito.mock(Font::class.java)

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.architecture.koin

import com.splendo.kaluga.architecture.viewmodel.ViewModel
import com.splendo.kaluga.test.architecture.UIThreadViewModelTest
import com.splendo.kaluga.test.koin.KoinUIThreadTest
import org.koin.core.module.Module
import org.koin.dsl.KoinAppDeclaration

abstract class KoinUIThreadViewModelTest<KVMC : KoinUIThreadViewModelTest.KoinViewModelTestContext<VM>, VM : ViewModel>(allowFreezing: Boolean = false) :
    KoinUIThreadTest<KVMC>(allowFreezing) {

    abstract class KoinViewModelTestContext<VM>(
        appDeclaration: KoinAppDeclaration? = null,
        koinModules: List<Module>
    ) : KoinUIThreadTest.KoinTestContext(appDeclaration, koinModules),
        UIThreadViewModelTest.ViewModelTestContext<VM> {
        constructor(vararg koinModules: Module) : this(null, koinModules.toList())
        constructor(appDeclaration: KoinAppDeclaration, vararg koinModules: Module) : this(
            appDeclaration,
            koinModules.toList()
        )
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.alerts

import co.touchlab.stately.concurrency.AtomicBoolean
import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.alerts.Alert
import com.splendo.kaluga.alerts.BaseAlertPresenter
import kotlinx.coroutines.CoroutineScope

class MockAlertPresenter(val alert: Alert) : BaseAlertPresenter(alert) {

    class Builder : BaseAlertPresenter.Builder() {

        val builtAlerts = mutableListOf<MockAlertPresenter>()

        override fun create(coroutineScope: CoroutineScope): MockAlertPresenter {
            return MockAlertPresenter(createAlert()).also {
                builtAlerts.add(it)
            }
        }
    }

    private var _isPresented = AtomicBoolean(false)
    var isPresented
        get() = _isPresented.value
        private set(value) { _isPresented.value = value }

    private var _afterHandler = AtomicReference<((Alert.Action?) -> Unit)?>(null)
    private var afterHandler: ((Alert.Action?) -> Unit)?
        get() = _afterHandler.get()
        set(value) = _afterHandler.set(value)

    fun closeWithAction(action: Alert.Action?) {
        action?.let {
            if (alert.actions.contains(it)) {
                it.handler()
                it
            } else {
                null
            }
        }.apply {
            afterHandler?.invoke(this)
        }
        dismissAlert(false)
    }

    override fun dismissAlert(animated: Boolean) {
        isPresented = false
        afterHandler = null
    }

    override fun showAlert(
        animated: Boolean,
        afterHandler: (Alert.Action?) -> Unit,
        completion: () -> Unit
    ) {
        isPresented = true
        this.afterHandler = afterHandler
        completion()
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.architecture

import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.Navigator

abstract class MockNavigator<A : NavigationAction<*>> : Navigator<A>

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import com.splendo.kaluga.base.utils.EmptyCompletableDeferred
import com.splendo.kaluga.base.utils.complete
import com.splendo.kaluga.bluetooth.Characteristic
import com.splendo.kaluga.bluetooth.CharacteristicWrapper
import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo

class MockCharacteristic(characteristic: CharacteristicWrapper, stateRepo: DeviceStateFlowRepo) : Characteristic(characteristic, stateRepo = stateRepo) {

    val didUpdate = EmptyCompletableDeferred()

    override suspend fun updateValue() {
        didUpdate.complete()
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import com.splendo.kaluga.bluetooth.CharacteristicWrapper

interface MockCharacteristicWrapper : CharacteristicWrapper, CanUpdateMockValue

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.test.mock.bluetooth

import com.splendo.kaluga.base.utils.EmptyCompletableDeferred
import com.splendo.kaluga.base.utils.complete
import com.splendo.kaluga.bluetooth.Descriptor
import com.splendo.kaluga.bluetooth.DescriptorWrapper
import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo

class MockDescriptor(descriptorWrapper: DescriptorWrapper, stateRepo: DeviceStateFlowRepo) : Descriptor(descriptorWrapper, stateRepo = stateRepo) {

    val didUpdate = EmptyCompletableDeferred()

    override suspend fun updateValue() {
        didUpdate.complete()
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import com.splendo.kaluga.bluetooth.DescriptorWrapper

interface MockDescriptorWrapper : DescriptorWrapper, CanUpdateMockValue

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import com.splendo.kaluga.bluetooth.ServiceWrapper
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo
import com.splendo.kaluga.bluetooth.device.DeviceWrapper
import com.splendo.kaluga.bluetooth.randomUUID

expect fun createDeviceWrapper(deviceName: String? = null): DeviceWrapper

expect fun createServiceWrapper(
    stateRepo: DeviceStateFlowRepo,
    uuid: UUID = randomUUID(),
    characteristics: List<Pair<UUID, List<UUID>>> = listOf(randomUUID() to listOf(randomUUID()))
): ServiceWrapper

interface CanUpdateMockValue {
    fun updateMockValue(value: ByteArray?)
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.hud

import co.touchlab.stately.concurrency.AtomicBoolean
import com.splendo.kaluga.hud.BaseHUD
import com.splendo.kaluga.hud.HudConfig
import kotlinx.coroutines.CoroutineScope

class MockHUD(override val hudConfig: HudConfig, coroutineScope: CoroutineScope) : BaseHUD(coroutineScope) {

    class Builder : BaseHUD.Builder() {

        val builtHUDs = mutableListOf<MockHUD>()

        override fun create(hudConfig: HudConfig, coroutineScope: CoroutineScope): MockHUD {
            return MockHUD(hudConfig, coroutineScope).also {
                builtHUDs.add(it)
            }
        }
    }

    private var _isVisible = AtomicBoolean(false)
    override var isVisible: Boolean
        get() = _isVisible.value
        private set(value) { _isVisible.value = value }

    override suspend fun present(animated: Boolean): BaseHUD {
        isVisible = true
        return this
    }

    override suspend fun dismiss(animated: Boolean) {
        isVisible = false
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.keyboard

import com.splendo.kaluga.keyboard.BaseKeyboardManager
import com.splendo.kaluga.keyboard.FocusHandler
import kotlinx.coroutines.CoroutineScope

class MockKeyboardManager : BaseKeyboardManager {

    class Builder : BaseKeyboardManager.Builder {

        val builtKeyboardManagers = mutableListOf<MockKeyboardManager>()

        override fun create(coroutineScope: CoroutineScope): MockKeyboardManager {
            return MockKeyboardManager().also { builtKeyboardManagers.add(it) }
        }
    }

    var focusHandler: FocusHandler? = null
        private set

    override fun show(focusHandler: FocusHandler) {
        this.focusHandler = focusHandler
    }

    override fun hide() {
        focusHandler = null
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.permissions

import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.permissions.PermissionContext
import com.splendo.kaluga.permissions.PermissionManager
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.bluetooth.BaseBluetoothPermissionManagerBuilder
import com.splendo.kaluga.permissions.bluetooth.BluetoothPermission
import com.splendo.kaluga.permissions.bluetooth.BluetoothPermissionStateRepo
import com.splendo.kaluga.permissions.calendar.BaseCalendarPermissionManagerBuilder
import com.splendo.kaluga.permissions.calendar.CalendarPermission
import com.splendo.kaluga.permissions.calendar.CalendarPermissionStateRepo
import com.splendo.kaluga.permissions.camera.BaseCameraPermissionManagerBuilder
import com.splendo.kaluga.permissions.camera.CameraPermission
import com.splendo.kaluga.permissions.camera.CameraPermissionStateRepo
import com.splendo.kaluga.permissions.contacts.BaseContactsPermissionManagerBuilder
import com.splendo.kaluga.permissions.contacts.ContactsPermission
import com.splendo.kaluga.permissions.contacts.ContactsPermissionStateRepo
import com.splendo.kaluga.permissions.location.BaseLocationPermissionManagerBuilder
import com.splendo.kaluga.permissions.location.LocationPermission
import com.splendo.kaluga.permissions.location.LocationPermissionStateRepo
import com.splendo.kaluga.permissions.microphone.BaseMicrophonePermissionManagerBuilder
import com.splendo.kaluga.permissions.microphone.MicrophonePermission
import com.splendo.kaluga.permissions.microphone.MicrophonePermissionStateRepo
import com.splendo.kaluga.permissions.notifications.BaseNotificationsPermissionManagerBuilder
import com.splendo.kaluga.permissions.notifications.NotificationsPermission
import com.splendo.kaluga.permissions.notifications.NotificationsPermissionStateRepo
import com.splendo.kaluga.permissions.storage.BaseStoragePermissionManagerBuilder
import com.splendo.kaluga.permissions.storage.StoragePermission
import com.splendo.kaluga.permissions.storage.StoragePermissionStateRepo
import com.splendo.kaluga.test.MockPermissionManager
import com.splendo.kaluga.test.MockPermissionStateRepo
import kotlin.coroutines.CoroutineContext

expect val mockPermissionContext: PermissionContext

class MockPermissionsBuilder : PermissionsBuilder(mockPermissionContext) {

    private val _cameraPMManager = AtomicReference<MockPermissionManager<CameraPermission>?>(null)
    var cameraPMManager: MockPermissionManager<CameraPermission>?
        get() = _cameraPMManager.get()
        set(value) = _cameraPMManager.set(value)

    private val cameraPMBuilder: BaseCameraPermissionManagerBuilder = object : BaseCameraPermissionManagerBuilder {
        override fun create(repo: CameraPermissionStateRepo): PermissionManager<CameraPermission> {
            cameraPMManager = MockPermissionManager(repo)
            return cameraPMManager!!
        }
    }

    private val _contactsPMManager = AtomicReference<MockPermissionManager<ContactsPermission>?>(null)
    var contactsPMManager: MockPermissionManager<ContactsPermission>?
        get() = _contactsPMManager.get()
        set(value) = _contactsPMManager.set(value)

    private val contactsPMBuilder: BaseContactsPermissionManagerBuilder = object : BaseContactsPermissionManagerBuilder {
        override fun create(contacts: ContactsPermission, repo: ContactsPermissionStateRepo): PermissionManager<ContactsPermission> {
            contactsPMManager = MockPermissionManager(repo)
            return contactsPMManager!!
        }
    }

    private val _microphonePMManager = AtomicReference<MockPermissionManager<MicrophonePermission>?>(null)
    var microphonePMManager: MockPermissionManager<MicrophonePermission>?
        get() = _microphonePMManager.get()
        set(value) = _microphonePMManager.set(value)

    private val microphonePMBuilder: BaseMicrophonePermissionManagerBuilder = object : BaseMicrophonePermissionManagerBuilder {
        override fun create(repo: MicrophonePermissionStateRepo): PermissionManager<MicrophonePermission> {
            microphonePMManager = MockPermissionManager(repo)
            return microphonePMManager!!
        }
    }

    private val _notificationsPMManager = AtomicReference<MockPermissionManager<NotificationsPermission>?>(null)
    var notificationsPMManager: MockPermissionManager<NotificationsPermission>?
        get() = _notificationsPMManager.get()
        set(value) = _notificationsPMManager.set(value)

    private val notificationsPMBuilder: BaseNotificationsPermissionManagerBuilder = object : BaseNotificationsPermissionManagerBuilder {
        override fun create(notifications: NotificationsPermission, repo: NotificationsPermissionStateRepo): PermissionManager<NotificationsPermission> {
            notificationsPMManager = MockPermissionManager(repo)
            return notificationsPMManager!!
        }
    }

    private val _bluetoothPMManager = AtomicReference<MockPermissionManager<BluetoothPermission>?>(null)
    var bluetoothPMManager: MockPermissionManager<BluetoothPermission>?
        get() = _bluetoothPMManager.get()
        set(value) = _bluetoothPMManager.set(value)

    private val bluetoothPMBuilder: BaseBluetoothPermissionManagerBuilder = object : BaseBluetoothPermissionManagerBuilder {
        override fun create(repo: BluetoothPermissionStateRepo): PermissionManager<BluetoothPermission> {
            bluetoothPMManager = MockPermissionManager(repo)
            return bluetoothPMManager!!
        }
    }

    private val _locationPMManager = AtomicReference<MockPermissionManager<LocationPermission>?>(null)
    var locationPMManager: MockPermissionManager<LocationPermission>?
        get() = _locationPMManager.get()
        set(value) = _locationPMManager.set(value)

    private val locationPMBuilder: BaseLocationPermissionManagerBuilder = object : BaseLocationPermissionManagerBuilder {
        override fun create(location: LocationPermission, repo: LocationPermissionStateRepo): PermissionManager<LocationPermission> {
            return MockPermissionManager(repo).also {
                locationPMManager = it
            }
        }
    }

    private val _calendarPMManager = AtomicReference<MockPermissionManager<CalendarPermission>?>(null)
    var calendarPMManager: MockPermissionManager<CalendarPermission>?
        get() = _calendarPMManager.get()
        set(value) = _calendarPMManager.set(value)

    private val calendarPMBuilder: BaseCalendarPermissionManagerBuilder = object : BaseCalendarPermissionManagerBuilder {
        override fun create(calendar: CalendarPermission, repo: CalendarPermissionStateRepo): PermissionManager<CalendarPermission> {
            calendarPMManager = MockPermissionManager(repo)
            return calendarPMManager!!
        }
    }

    private val _storagePMManager = AtomicReference<MockPermissionManager<StoragePermission>?>(null)
    var storagePMManager: MockPermissionManager<StoragePermission>?
        get() = _storagePMManager.get()
        set(value) = _storagePMManager.set(value)

    private val storagePMBuilder: BaseStoragePermissionManagerBuilder = object : BaseStoragePermissionManagerBuilder {
        override fun create(storage: StoragePermission, repo: StoragePermissionStateRepo): PermissionManager<StoragePermission> {
            storagePMManager = MockPermissionManager(repo)
            return storagePMManager!!
        }
    }

    fun registerAllPermissionsBuilders() {
        register(bluetoothPMBuilder, BluetoothPermission::class).also { builder ->
            registerPermissionStateRepoBuilder(BluetoothPermission::class) { permission, coroutineContext ->
                MockBluetoothPermissionStateRepo(builder, coroutineContext)
            }
        }
        register(locationPMBuilder, LocationPermission::class).also { builder ->
            registerPermissionStateRepoBuilder(LocationPermission::class) { permission, coroutineContext ->
                MockLocationPermissionStateRepo(permission as LocationPermission, builder, coroutineContext)
            }
        }
        register(calendarPMBuilder, CalendarPermission::class).also { builder ->
            registerPermissionStateRepoBuilder(CalendarPermission::class) { permission, coroutineContext ->
                MockPermissionStateRepo<CalendarPermission>()
            }
        }
        register(storagePMBuilder, StoragePermission::class).also { builder ->
            registerPermissionStateRepoBuilder(StoragePermission::class) { permission, coroutineContext ->
                MockPermissionStateRepo<StoragePermission>()
            }
        }
        register(cameraPMBuilder, CameraPermission::class).also { builder ->
            registerPermissionStateRepoBuilder(CameraPermission::class) { permission, coroutineContext ->
                MockPermissionStateRepo<CameraPermission>()
            }
        }
        register(contactsPMBuilder, ContactsPermission::class).also { builder ->
            registerPermissionStateRepoBuilder(ContactsPermission::class) { permission, coroutineContext ->
                MockPermissionStateRepo<ContactsPermission>()
            }
        }
        register(microphonePMBuilder, MicrophonePermission::class).also { builder ->
            registerPermissionStateRepoBuilder(MicrophonePermission::class) { permission, coroutineContext ->
                MockPermissionStateRepo<MicrophonePermission>()
            }
        }
        register(notificationsPMBuilder, NotificationsPermission::class).also { builder ->
            registerPermissionStateRepoBuilder(NotificationsPermission::class) { permission, coroutineContext ->
                MockPermissionStateRepo<NotificationsPermission>()
            }
        }
    }

    private class MockBluetoothPermissionStateRepo(
        builder: BaseBluetoothPermissionManagerBuilder,
        coroutineContext: CoroutineContext
    ) : BluetoothPermissionStateRepo(coroutineContext) {
        private val _permissionManager = AtomicReference<PermissionManager<BluetoothPermission>?>(null)
        override val permissionManager: PermissionManager<BluetoothPermission> by lazy { _permissionManager.get()!! }

        init {
            _permissionManager.set(builder.create(this))
        }
    }

    private class MockLocationPermissionStateRepo(
        location: LocationPermission,
        builder: BaseLocationPermissionManagerBuilder,
        coroutineContext: CoroutineContext
    ) : LocationPermissionStateRepo(coroutineContext) {
        private val _permissionManager = AtomicReference<PermissionManager<LocationPermission>?>(null)
        override val permissionManager: PermissionManager<LocationPermission> by lazy { _permissionManager.get()!! }

        init {
            _permissionManager.set(builder.create(location, this))
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.resources

import com.splendo.kaluga.base.text.format
import com.splendo.kaluga.resources.Color
import com.splendo.kaluga.resources.ColorLoader
import com.splendo.kaluga.resources.Font
import com.splendo.kaluga.resources.FontLoader
import com.splendo.kaluga.resources.Image
import com.splendo.kaluga.resources.ImageLoader
import com.splendo.kaluga.resources.StringLoader

class MockStringLoader private constructor (
    private val string: String?,
    private val quantityString: String?,
    private val stringMap: Map<String, String?>,
    private val quantityStringMap: Map<String, String?>
) : StringLoader {
    constructor(string: String? = null, quantityString: String? = null) :
        this(string, quantityString, emptyMap(), emptyMap())
    constructor(stringMap: Map<String, String?>, quantityStringMap: Map<String, String?>) :
        this(null, null, stringMap, quantityStringMap)

    override fun loadString(identifier: String, defaultValue: String): String =
        stringMap[identifier] ?: string ?: defaultValue

    override fun loadQuantityString(
        identifier: String,
        quantity: Int,
        defaultValue: String
    ): String =
        (quantityStringMap[identifier] ?: quantityString)?.format(quantity) ?: defaultValue
}

class MockColorLoader private constructor (
    private val color: Color?,
    private val colorMap: Map<String, Color?>
) : ColorLoader {
    constructor(color: Color? = null) : this(color, emptyMap())
    constructor(colorMap: Map<String, Color?>) : this(null, colorMap)

    override fun loadColor(identifier: String, defaultValue: Color?): Color? =
        colorMap[identifier] ?: color ?: defaultValue
}

class MockImageLoader private constructor (
    private val image: Image?,
    private val imageMap: Map<String, Image?>
) : ImageLoader {
    constructor(image: Image? = null) : this(image, emptyMap())
    constructor(imageMap: Map<String, Image?>) : this(null, imageMap)

    override fun loadImage(identifier: String, defaultValue: Image?): Image? =
        imageMap[identifier] ?: image ?: defaultValue
}

class MockFontLoader private constructor (
    private val font: Font?,
    private val fontMap: Map<String, Font?>
) : FontLoader {
    constructor(font: Font? = null) : this(font, emptyMap())
    constructor(fontMap: Map<String, Font?>) : this(null, fontMap)

    override suspend fun loadFont(identifier: String, defaultValue: Font?): Font? =
        fontMap[identifier] ?: font ?: defaultValue
}

expect fun mockColor(): Color
expect fun mockImage(): Image
expect fun mockFont(): Font

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.base.toNSData
import com.splendo.kaluga.base.utils.EmptyCompletableDeferred
import com.splendo.kaluga.base.utils.complete
import com.splendo.kaluga.bluetooth.DescriptorWrapper
import kotlinx.coroutines.CompletableDeferred
import platform.CoreBluetooth.CBPeripheral
import platform.CoreBluetooth.CBUUID
import platform.Foundation.NSData

class IOSMockCharacteristicWrapper(
    override val uuid: CBUUID = CBUUID(),
    descriptorUuids: List<CBUUID> = emptyList()
) : MockCharacteristicWrapper {

    val isReadCompleted = EmptyCompletableDeferred()
    val isWriteCompleted = CompletableDeferred<NSData>()
    val isNotificationCompleted = CompletableDeferred<Boolean>()

    val _value = AtomicReference<NSData?>(null)
    override var value: NSData?
        get() = _value.get()
        set(value) { _value.set(value) }

    override val descriptors: List<DescriptorWrapper> = descriptorUuids.map {
        IOSMockDescriptorWrapper(
            it
        )
    }

    override fun readValue(peripheral: CBPeripheral) {
        isReadCompleted.complete()
    }

    override fun setNotificationValue(enabled: Boolean, peripheral: CBPeripheral) {
        isNotificationCompleted.complete(enabled)
    }

    override fun writeValue(value: NSData, peripheral: CBPeripheral) {
        this.value = value
        isWriteCompleted.complete(value)
    }

    override fun updateMockValue(value: ByteArray?) {
        this.value = value?.toNSData() ?: NSData()
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.base.toNSData
import com.splendo.kaluga.base.utils.EmptyCompletableDeferred
import com.splendo.kaluga.base.utils.complete
import kotlinx.coroutines.CompletableDeferred
import platform.CoreBluetooth.CBPeripheral
import platform.CoreBluetooth.CBUUID
import platform.Foundation.NSData

class IOSMockDescriptorWrapper(override val uuid: CBUUID = CBUUID()) : MockDescriptorWrapper {

    val isReadCompleted = EmptyCompletableDeferred()
    val isWriteCompleted = CompletableDeferred<NSData>()

    override fun updateMockValue(value: ByteArray?) {
        this.value = value?.toNSData()
    }

    private val _value = AtomicReference<NSData?>(null)
    override var value: NSData?
        get() = _value.get()
        set(value) { _value.set(value) }

    override fun readValue(peripheral: CBPeripheral) {
        isReadCompleted.complete()
    }

    override fun writeValue(value: NSData, peripheral: CBPeripheral) {
        this.value = value
        isWriteCompleted.complete(value)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import com.splendo.kaluga.bluetooth.device.DeviceWrapper
import platform.Foundation.NSUUID

data class MockCBPeripheralWrapper(
    override val name: String? = null,
    override val identifier: NSUUID = NSUUID()
) : DeviceWrapper

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import com.splendo.kaluga.bluetooth.CharacteristicWrapper
import com.splendo.kaluga.bluetooth.ServiceWrapper
import com.splendo.kaluga.bluetooth.UUID

class MockServiceWrapper(
    override val uuid: UUID = UUID(),
    characteristicUuids: List<Pair<UUID, List<UUID>>> = emptyList()
) : ServiceWrapper {

    override val characteristics: List<CharacteristicWrapper> = characteristicUuids.map {
        IOSMockCharacteristicWrapper(
            it.first,
            it.second
        )
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import com.splendo.kaluga.bluetooth.ServiceWrapper
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo
import com.splendo.kaluga.bluetooth.device.DeviceWrapper
import platform.CoreBluetooth.CBUUID

actual fun createDeviceWrapper(
    deviceName: String?
): DeviceWrapper = MockCBPeripheralWrapper(name = deviceName)

actual fun createServiceWrapper(
    stateRepo: DeviceStateFlowRepo,
    uuid: UUID,
    characteristics: List<Pair<CBUUID, List<CBUUID>>>
): ServiceWrapper =
    MockServiceWrapper(uuid, characteristics)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.permissions

import platform.Foundation.NSBundle

actual val mockPermissionContext get() = NSBundle()

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.resources

import com.splendo.kaluga.resources.Color
import com.splendo.kaluga.resources.Font
import com.splendo.kaluga.resources.Image
import platform.UIKit.UIColor

actual fun mockColor(): Color = Color(UIColor.blackColor())
actual fun mockImage(): Image = Image()
actual fun mockFont(): Font = Font()

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import com.splendo.kaluga.bluetooth.ServiceWrapper
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo
import com.splendo.kaluga.bluetooth.device.DeviceWrapper

actual fun createDeviceWrapper(deviceName: String?): DeviceWrapper {
    TODO()
}

actual fun createServiceWrapper(
    stateRepo: DeviceStateFlowRepo,
    uuid: UUID,
    characteristics: List<Pair<UUID, List<UUID>>>
): ServiceWrapper = TODO()

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.permissions

import com.splendo.kaluga.permissions.PermissionContext

actual val mockPermissionContext = PermissionContext()

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.resources

import com.splendo.kaluga.resources.Color
import com.splendo.kaluga.resources.Font
import com.splendo.kaluga.resources.Image

actual fun mockColor(): Color = 0
actual fun mockImage(): Image = Image()
actual fun mockFont(): Font = Font()

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import com.splendo.kaluga.bluetooth.device.DeviceWrapper
import com.splendo.kaluga.bluetooth.device.Identifier

class MockDeviceWrapper(override val name: String?, override val identifier: Identifier) :
    DeviceWrapper

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth

import com.splendo.kaluga.bluetooth.ServiceWrapper
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo
import com.splendo.kaluga.bluetooth.device.DeviceWrapper
import com.splendo.kaluga.bluetooth.randomUUID

actual fun createDeviceWrapper(deviceName: String?): DeviceWrapper =
    MockDeviceWrapper(deviceName, randomUUID())

actual fun createServiceWrapper(
    stateRepo: DeviceStateFlowRepo,
    uuid: UUID,
    characteristics: List<Pair<UUID, List<UUID>>>
): ServiceWrapper = TODO()

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.resources

import com.splendo.kaluga.resources.Color
import com.splendo.kaluga.resources.Font
import com.splendo.kaluga.resources.Image

actual fun mockColor(): Color = 0
actual fun mockImage(): Image = Image()
actual fun mockFont(): Font = Font()

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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 architecture

/* ktlint-disable no-wildcard-imports */
import com.splendo.kaluga.alerts.AlertPresenter
import com.splendo.kaluga.architecture.navigation.NavigationSpec
import com.splendo.kaluga.architecture.navigation.ViewControllerNavigator
import com.splendo.kaluga.architecture.observable.Disposable
import com.splendo.kaluga.architecture.observable.DisposeBag
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.architecture.viewmodel.LifecycleManager
import com.splendo.kaluga.architecture.viewmodel.addLifecycleManager
import com.splendo.kaluga.architecture.viewmodel.onLifeCycleChanged
import com.splendo.kaluga.bluetooth.Bluetooth
import com.splendo.kaluga.bluetooth.beacons.Beacons
import com.splendo.kaluga.bluetooth.device.Identifier
import com.splendo.kaluga.example.shared.viewmodel.ExampleTabNavigation
import com.splendo.kaluga.example.shared.viewmodel.ExampleViewModel
import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel
import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureInputViewModel
import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails
import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListViewModel
import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDeviceDetailViewModel
import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListViewModel
import com.splendo.kaluga.example.shared.viewmodel.bluetooth.DeviceDetailsSpecRow
import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListNavigationAction
import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListViewModel
import com.splendo.kaluga.example.shared.viewmodel.info.*
import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel
import com.splendo.kaluga.example.shared.viewmodel.link.BrowserNavigationActions
import com.splendo.kaluga.example.shared.viewmodel.link.BrowserSpecRow
import com.splendo.kaluga.example.shared.viewmodel.link.LinksViewModel
import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel
import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionNavigationBundleSpecRow
import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionView
import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionViewModel
import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionsListViewModel
import com.splendo.kaluga.example.shared.viewmodel.resources.ButtonViewModel
import com.splendo.kaluga.example.shared.viewmodel.resources.ColorViewModel
import com.splendo.kaluga.example.shared.viewmodel.resources.LabelViewModel
import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListNavigationAction
import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListViewModel
import com.splendo.kaluga.example.shared.viewmodel.system.SystemNavigationActions
import com.splendo.kaluga.example.shared.viewmodel.system.SystemViewModel
import com.splendo.kaluga.keyboard.FocusHandler
import com.splendo.kaluga.keyboard.KeyboardManager
import com.splendo.kaluga.links.LinksBuilder
import com.splendo.kaluga.location.LocationStateRepoBuilder
import com.splendo.kaluga.permissions.Permission
import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.permissions.bluetooth.BluetoothPermission
import com.splendo.kaluga.permissions.calendar.CalendarPermission
import com.splendo.kaluga.permissions.camera.CameraPermission
import com.splendo.kaluga.permissions.contacts.ContactsPermission
import com.splendo.kaluga.permissions.location.LocationPermission
import com.splendo.kaluga.permissions.microphone.MicrophonePermission
import com.splendo.kaluga.permissions.notifications.*
import com.splendo.kaluga.permissions.storage.StoragePermission
import com.splendo.kaluga.resources.StyledStringBuilder
import com.splendo.kaluga.review.ReviewManager
import platform.Foundation.NSURL
import platform.Foundation.NSUUID
import platform.UIKit.*
import platform.UserNotifications.UNAuthorizationOptionAlert
import platform.UserNotifications.UNAuthorizationOptionSound

class KNArchitectureFramework {

    fun createExampleViewModel(
        parent: UIViewController,
        containerView: UIView,
        featuresList: () -> UIViewController,
        info: () -> UIViewController
    ): ExampleViewModel {
        return ExampleViewModel(
            ViewControllerNavigator(parent) { action ->
                NavigationSpec.Nested(
                    NavigationSpec.Nested.Type.Replace(1),
                    containerView,
                    when (action) {
                        is ExampleTabNavigation.FeatureList -> featuresList
                        is ExampleTabNavigation.Info -> info
                    }
                )
            }
        )
    }

    fun createFeatureListViewModel(parent: UIViewController): FeatureListViewModel {
        return FeatureListViewModel(
            ViewControllerNavigator(parent) { action ->
                NavigationSpec.Segue(
                    when (action) {
                        is FeatureListNavigationAction.Location -> "showLocation"
                        is FeatureListNavigationAction.Permissions -> "showPermissions"
                        is FeatureListNavigationAction.Alerts -> "showAlerts"
                        is FeatureListNavigationAction.DateTimePicker -> "showDateTimePicker"
                        is FeatureListNavigationAction.LoadingIndicator -> "showHUD"
                        is FeatureListNavigationAction.Architecture -> "showArchitecture"
                        is FeatureListNavigationAction.Keyboard -> "showKeyboard"
                        is FeatureListNavigationAction.System -> "showSystem"
                        is FeatureListNavigationAction.Links -> "showLinks"
                        is FeatureListNavigationAction.Bluetooth -> "showBluetooth"
                        is FeatureListNavigationAction.Beacons -> "showBeacons"
                        is FeatureListNavigationAction.PlatformSpecific -> "showPlatformSpecific"
                        is FeatureListNavigationAction.Resources -> "showResources"
                    }
                )
            }
        )
    }

    fun createInfoViewModel(parent: UIViewController): InfoViewModel {
        return InfoViewModel(
            ReviewManager.Builder(),
            ViewControllerNavigator(parent) { action ->
                when (action) {
                    is InfoNavigation.Dialog -> {
                        NavigationSpec.Present(
                            true,
                            present = {
                                val title = action.bundle?.get(DialogSpecRow.TitleRow) ?: ""
                                val message = action.bundle?.get(DialogSpecRow.MessageRow) ?: ""
                                UIAlertController.alertControllerWithTitle(title, message, UIAlertControllerStyleAlert).apply {
                                    addAction(UIAlertAction.actionWithTitle("OK", UIAlertActionStyleDefault) {})
                                }
                            }
                        )
                    }
                    is InfoNavigation.Link -> NavigationSpec.Browser(
                        NSURL.URLWithString(
                            action.bundle!!.get(LinkSpecRow.LinkRow)
                        )!!,
                        NavigationSpec.Browser.Type.Normal
                    )
                    is InfoNavigation.Mail -> NavigationSpec.Email(
                        NavigationSpec.Email.EmailSettings(
                            to = action.bundle?.get(
                                MailSpecRow.ToRow
                            ) ?: emptyList(),
                            subject = action.bundle?.get(MailSpecRow.SubjectRow)
                        )
                    )
                }
            }
        )
    }

    fun createBluetoothListViewModel(parent: UIViewController, bluetooth: Bluetooth, createDeviceDetailsViewController: (Identifier, Bluetooth) -> UIViewController): BluetoothListViewModel {
        return BluetoothListViewModel(
            bluetooth,
            ViewControllerNavigator(parent) { action ->
                NavigationSpec.Push(
                    push = {
                        val bundle = action.bundle ?: return@Push UIViewController()
                        val identifier = NSUUID(uUIDString = bundle.get(DeviceDetailsSpecRow.UUIDRow))
                        createDeviceDetailsViewController(identifier, bluetooth)
                    }
                )
            }
        )
    }

    fun createBluetoothDeviceDetailsViewModel(identifier: Identifier, bluetooth: Bluetooth): BluetoothDeviceDetailViewModel {
        return BluetoothDeviceDetailViewModel(bluetooth, identifier)
    }

    fun createBeaconsListViewModel(parent: UIViewController, service: Beacons): BeaconsListViewModel {
        return BeaconsListViewModel(service)
    }

    fun createPermissionListViewModel(parent: UIViewController, createPermissionViewController: (Permission) -> UIViewController): PermissionsListViewModel {
        return PermissionsListViewModel(
            ViewControllerNavigator(parent) { action ->
                NavigationSpec.Push(
                    push = {
                        val bundle = action.bundle ?: return@Push UIViewController()
                        val permission = when (bundle.get(PermissionNavigationBundleSpecRow)) {
                            PermissionView.Bluetooth -> BluetoothPermission
                            PermissionView.Calendar -> CalendarPermission()
                            PermissionView.Camera -> CameraPermission
                            PermissionView.Contacts -> ContactsPermission()
                            PermissionView.Location -> LocationPermission(
                                background = true,
                                precise = true
                            )
                            PermissionView.Microphone -> MicrophonePermission
                            PermissionView.Notifications -> NotificationsPermission(
                                NotificationOptions(
                                    UNAuthorizationOptionAlert or UNAuthorizationOptionSound
                                )
                            )
                            PermissionView.Storage -> StoragePermission()
                        }
                        createPermissionViewController(permission)
                    }
                )
            }
        )
    }

    fun createPermissionViewModel(permissions: Permissions, permission: Permission): PermissionViewModel {
        return PermissionViewModel(permissions, permission)
    }

    fun createLocationViewModel(permission: LocationPermission, repoBuilder: LocationStateRepoBuilder): LocationViewModel {
        return LocationViewModel(permission, repoBuilder)
    }

    fun createArchitectureInputViewModel(parent: UIViewController, createDetailsViewController: (InputDetails) -> UIViewController): ArchitectureInputViewModel {
        return ArchitectureInputViewModel(
            ViewControllerNavigator(parent) { action ->
                NavigationSpec.Present(
                    present = {
                        createDetailsViewController(action.bundle?.get(action.type) ?: InputDetails("", 0))
                    }
                )
            }
        )
    }

    fun createArchitectureDetailsViewModel(parent: UIViewController, inputDetails: InputDetails, onDismiss: (InputDetails) -> Unit): ArchitectureDetailsViewModel {
        return ArchitectureDetailsViewModel(
            inputDetails,
            ViewControllerNavigator(parent) { action ->
                NavigationSpec.Dismiss(
                    completion = {
                        onDismiss(action.bundle?.get(action.type) ?: InputDetails("", 0))
                    }
                )
            }
        )
    }

    fun createKeyboardViewModel(focusHandler: FocusHandler): KeyboardViewModel {
        return KeyboardViewModel(KeyboardManager.Builder(), focusHandler)
    }

    fun createSystemViewModel(parent: UIViewController): SystemViewModel {
        return SystemViewModel(
            ViewControllerNavigator(parent) { action ->
                when (action) {
                    SystemNavigationActions.Network ->
                        NavigationSpec.Segue("showNetwork")
                }
            }
        )
    }

    fun createResourcesViewModel(parent: UIViewController): ResourcesListViewModel {
        return ResourcesListViewModel(
            ViewControllerNavigator(parent) { action ->
                NavigationSpec.Segue(
                    when (action) {
                        is ResourcesListNavigationAction.Button -> "showButton"
                        is ResourcesListNavigationAction.Color -> "showColor"
                        is ResourcesListNavigationAction.Label -> "showLabel"
                    }
                )
            }
        )
    }

    fun createButtonViewModel(parent: UIViewController) = ButtonViewModel(
        StyledStringBuilder.Provider(),
        AlertPresenter.Builder(parent)
    )

    fun createLabelViewModel() = LabelViewModel(StyledStringBuilder.Provider())

    fun createColorViewModel(parent: UIViewController) = ColorViewModel(AlertPresenter.Builder(parent))

    fun <VM : BaseViewModel> bind(viewModel: VM, to: UIViewController, onLifecycleChanges: onLifeCycleChanged): LifecycleManager {
        return viewModel.addLifecycleManager(to, onLifecycleChanges)
    }

    fun createLinksViewModel(
        parent: UIViewController,
        animated: Boolean,
        completion: (() -> Unit)? = null
    ): LinksViewModel {
        return LinksViewModel(
            LinksBuilder(),
            AlertPresenter.Builder(parent),
            ViewControllerNavigator(parent) { action ->
                when (action) {
                    is BrowserNavigationActions.OpenWebView -> NavigationSpec.Browser(
                        NSURL.URLWithString(action.bundle!!.get(BrowserSpecRow.UrlSpecRow))!!,
                        NavigationSpec.Browser.Type.Normal
                    )
                }
            }
        )
    }
}

fun ExampleViewModel.observeTabs(stackView: UIStackView, addOnPressed: (UIButton, () -> Unit) -> Unit): List<Disposable> {
    val selectedButtonDisposeBag = DisposeBag()
    return listOf(
        tabs.observeInitialized { tabs ->
            selectedButtonDisposeBag.dispose()
            stackView.arrangedSubviews.forEach { subView -> (subView as UIView).removeFromSuperview() }
            tabs.forEach { tab ->
                val button = UIButton()
                button.setTitle(tab.title, UIControlStateNormal)
                button.setTitleColor(UIColor.systemBlueColor, UIControlStateSelected)
                button.setTitleColor(UIColor.systemBlueColor, UIControlStateHighlighted)
                button.setTitleColor(UIColor.grayColor, UIControlStateNormal)
                this.tab.observeInitialized { selectedTab ->
                    button.setSelected(selectedTab == tab)
                }.addTo(selectedButtonDisposeBag)
                addOnPressed(button) {
                    this.tab.post(tab)
                }
                stackView.addArrangedSubview(button)
            }
        }
    )
}

fun SystemViewModel.observeModules(onModuleChanged: (List<String>, (Int) -> Unit) -> Unit): Disposable =
    modules.observeInitialized { modules ->
        val moduleName = modules.map { it.name }
        onModuleChanged(moduleName) { onButtonTapped(modules[it]) }
    }

fun FeatureListViewModel.observeFeatures(onFeaturesChanged: (List<String>, (Int) -> Unit) -> Unit): Disposable {
    return feature.observeInitialized { features ->
        val titles = features.map { feature -> feature.title }
        onFeaturesChanged(titles) { index -> this.onFeaturePressed(features[index]) }
    }
}

fun InfoViewModel.observeButtons(onInfoButtonsChanged: (List<String>, (Int) -> Unit) -> Unit): Disposable {
    return buttons.observeInitialized { buttons ->
        val titles = buttons.map { button -> button.title }
        onInfoButtonsChanged(titles) { index -> this.onButtonPressed(buttons[index]) }
    }
}

fun PermissionsListViewModel.observePermissions(onPermissionsChanged: (List<PermissionView>, (Int) -> Unit) -> Unit): Disposable {
    return permissions.observeInitialized { permissions ->
        onPermissionsChanged(permissions) { index -> this.onPermissionPressed(permissions[index]) }
    }
}

fun ResourcesListViewModel.observeResources(onResourcesChanged: (List<String>, (Int) -> Unit) -> Unit): Disposable {
    return resources.observeInitialized { features ->
        val titles = features.map { feature -> feature.title }
        onResourcesChanged(titles) { index -> this.onResourceSelected(features[index]) }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.ios.beacons

import com.splendo.kaluga.bluetooth.BluetoothBuilder
import com.splendo.kaluga.bluetooth.beacons.Beacons
import kotlinx.coroutines.MainScope

class KNBeaconsFramework {
    private val mainScope = MainScope()
    val service = Beacons(
        BluetoothBuilder().create(coroutineScope = mainScope),
        timeoutMs = 60_000
    )
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.ios.bluetooth

import com.splendo.kaluga.bluetooth.BluetoothBuilder
import kotlinx.coroutines.MainScope

class KNBluetoothFramework {
    val mainScope = MainScope()
    val bluetooth = BluetoothBuilder().create(coroutineScope = mainScope)
}

package location

import com.splendo.kaluga.location.LocationStateRepoBuilder
import permissions.KNPermissionsFramework

class KNLocationFramework {
    fun getPermissionRepoBuilder(): LocationStateRepoBuilder { return LocationStateRepoBuilder(permissions = KNPermissionsFramework().getPermissions()) }
}

package permissions

import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermission
import com.splendo.kaluga.permissions.calendar.registerCalendarPermission
import com.splendo.kaluga.permissions.camera.registerCameraPermission
import com.splendo.kaluga.permissions.contacts.registerContactsPermission
import com.splendo.kaluga.permissions.location.registerLocationPermission
import com.splendo.kaluga.permissions.microphone.registerMicrophonePermission
import com.splendo.kaluga.permissions.notifications.registerNotificationsPermission
import com.splendo.kaluga.permissions.storage.registerStoragePermission

class KNPermissionsFramework {
    fun getPermissions(): Permissions {
        return Permissions(
            PermissionsBuilder().apply {
                registerBluetoothPermission()
                registerCameraPermission()
                registerStoragePermission()
                registerLocationPermission()
                registerNotificationsPermission()
                registerContactsPermission()
                registerMicrophonePermission()
                registerCalendarPermission()
            }
        )
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.architecture

import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction
import com.splendo.kaluga.architecture.observable.InitializedObservable
import com.splendo.kaluga.architecture.observable.subjectOf
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import kotlinx.serialization.Serializable

@Serializable
data class InputDetails(
    val name: String,
    val number: Int
)

class CloseDetailsNavigation(inputDetails: InputDetails) : SingleValueNavigationAction<InputDetails>(
    inputDetails,
    NavigationBundleSpecType.SerializedType(InputDetails.serializer())
)

class ArchitectureDetailsViewModel(initialDetail: InputDetails, navigator: Navigator<CloseDetailsNavigation>) : NavigatingViewModel<CloseDetailsNavigation>(navigator) {

    private val _name = subjectOf(initialDetail.name)
    val name: InitializedObservable<String> = _name
    private val _number = subjectOf(initialDetail.number.toString())
    val number: InitializedObservable<String> = _number

    private var nameResult: String by _name.valueDelegate
    private var numberResult: String by _number.valueDelegate

    fun onInversePressed() {
        nameResult = nameResult.reversed()
        numberResult = numberResult.reversed()
    }

    fun onClosePressed() {
        navigator.navigate(CloseDetailsNavigation(InputDetails(nameResult, numberResult.toIntOrNull() ?: 0)))
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.architecture

import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction
import com.splendo.kaluga.architecture.observable.ObservableOptional
import com.splendo.kaluga.architecture.observable.observableOf
import com.splendo.kaluga.architecture.observable.toInitializedSubject
import com.splendo.kaluga.architecture.observable.toUninitializedObservable
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

class InputNavigation(inputDetails: InputDetails) : SingleValueNavigationAction<InputDetails>(
    inputDetails,
    NavigationBundleSpecType.SerializedType(InputDetails.serializer())
)

class ArchitectureInputViewModel(navigator: Navigator<SingleValueNavigationAction<InputDetails>>) : NavigatingViewModel<SingleValueNavigationAction<InputDetails>>(navigator) {

    val nameHeader = observableOf("Enter your Name")
    val numberHeader = observableOf("Enter a Number")

    private val _nameInput = MutableStateFlow("")
    val nameInput = _nameInput.toInitializedSubject(coroutineScope)

    private val _numberInput = MutableStateFlow("")
    val numberInput = _numberInput.toInitializedSubject(coroutineScope)

    private val _isNameValid: Flow<Boolean> get() {
        return _nameInput.map {
            it.isNotEmpty()
        }
    }
    val isNameValid = _isNameValid.toUninitializedObservable(coroutineScope)

    private val _isNumberValid: Flow<Boolean> get() {
        return _numberInput.map {
            it.toIntOrNull() != null
        }
    }
    val isNumberValid = _isNumberValid.toUninitializedObservable(coroutineScope)

    private val isValid = combine(_isNameValid, _isNumberValid) {
        validName, validNumber ->
        validName && validNumber
    }

    fun onShowDetailsPressed() {
        val nameResult: ObservableOptional<String> by nameInput
        val name: String? by nameResult
        val numberResult: ObservableOptional<String> by numberInput
        val number: String? by numberResult
        coroutineScope.launch {
            if (isValid.first()) {
                navigator.navigate(
                    InputNavigation(
                        InputDetails(name ?: "", number?.toIntOrNull() ?: 0)
                    )
                )
            }
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.beacons

import com.splendo.kaluga.architecture.observable.toInitializedObservable
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.base.text.format
import com.splendo.kaluga.bluetooth.beacons.BeaconInfo
import com.splendo.kaluga.bluetooth.beacons.Beacons
import com.splendo.kaluga.bluetooth.beacons.get
import com.splendo.kaluga.bluetooth.device.Identifier
import com.splendo.kaluga.resources.localized
import kotlinx.coroutines.flow.map

class BeaconsListBeaconViewModel(identifier: Identifier, service: Beacons) : BaseViewModel() {

    private val beacon = service.beacons[identifier]

    private fun <T> beaconInfoObservable(mapper: (BeaconInfo?) -> T) = beacon
        .map { mapper(it) }
        .toInitializedObservable(null, coroutineScope)

    val namespace = beaconInfoObservable { it?.beaconID?.namespace ?: "" }
    val instance = beaconInfoObservable { it?.beaconID?.instance ?: "" }
    val txPower = beaconInfoObservable {
        it?.txPower?.let { txPower ->
            "txPower".localized().format(txPower)
        } ?: ""
    }

    public override fun onCleared() = super.onCleared()
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.beacons

import com.splendo.kaluga.architecture.observable.toInitializedObservable
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.bluetooth.beacons.Beacons
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

class BeaconsListViewModel(private val service: Beacons) : BaseViewModel() {

    private val _isScanning = MutableStateFlow(false)
    val isScanning = _isScanning.toInitializedObservable(coroutineScope)

    private val _beacons = MutableStateFlow<List<BeaconsListBeaconViewModel>>(emptyList())
    val beacons = _beacons.toInitializedObservable(coroutineScope)

    override fun onResume(scope: CoroutineScope) {
        super.onResume(scope)

        scope.launch {
            service
                .isMonitoring()
                .collect { _isScanning.value = it }
        }

        scope.launch {
            service.beacons
                .map { beacons ->
                    beacons.map { beacon ->
                        BeaconsListBeaconViewModel(beacon.identifier, service)
                    }
                }
                .collect { beacons ->
                    cleanDevices()
                    _beacons.value = beacons
                }
        }
    }

    fun onScanPressed() {
        if (_isScanning.value) {
            service.stopMonitoring()
        } else {
            service.startMonitoring(coroutineScope)
        }
    }

    override fun onCleared() {
        super.onCleared()
        cleanDevices()
    }

    private fun cleanDevices() {
        _beacons.value.forEach { it.onCleared() }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.bluetooth

import com.splendo.kaluga.architecture.observable.toInitializedObservable
import com.splendo.kaluga.architecture.observable.toUninitializedObservable
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.base.utils.toHexString
import com.splendo.kaluga.bluetooth.Bluetooth
import com.splendo.kaluga.bluetooth.Characteristic
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.characteristics
import com.splendo.kaluga.bluetooth.descriptors
import com.splendo.kaluga.bluetooth.device.Identifier
import com.splendo.kaluga.bluetooth.get
import com.splendo.kaluga.bluetooth.services
import com.splendo.kaluga.bluetooth.uuidString
import com.splendo.kaluga.bluetooth.value
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

class BluetoothCharacteristicViewModel(private val bluetooth: Bluetooth, private val deviceIdentifier: Identifier, private val serviceUUID: UUID, private val characteristicUUID: UUID) : BaseViewModel() {

    private val characteristic: Flow<Characteristic?> get() = bluetooth.devices()[deviceIdentifier].services()[serviceUUID].characteristics()[characteristicUUID]

    val uuid = characteristicUUID.uuidString
    val value = characteristic.value().map { it?.toHexString() ?: "" }.toUninitializedObservable(coroutineScope)

    private val _descriptors = MutableStateFlow(emptyList<BluetoothDescriptorViewModel>())
    val descriptors = _descriptors.toInitializedObservable(coroutineScope)

    override fun onResume(scope: CoroutineScope) {
        super.onResume(scope)

        scope.launch { characteristic.flatMapLatest { characteristic -> characteristic?.let { flowOf(it) } ?: emptyFlow() }.first().readValue() }
        scope.launch {
            characteristic.descriptors().map { descriptors -> descriptors.map { BluetoothDescriptorViewModel(bluetooth, deviceIdentifier, serviceUUID, characteristicUUID, it.uuid) } }.collect {
                clearDescriptors()
                _descriptors.value = it
            }
        }
    }

    public override fun onCleared() {
        super.onCleared()
        clearDescriptors()
    }

    private fun clearDescriptors() {
        _descriptors.value.forEach { it.onCleared() }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.bluetooth

import com.splendo.kaluga.architecture.observable.toUninitializedObservable
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.base.utils.toHexString
import com.splendo.kaluga.bluetooth.Bluetooth
import com.splendo.kaluga.bluetooth.Descriptor
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.characteristics
import com.splendo.kaluga.bluetooth.descriptors
import com.splendo.kaluga.bluetooth.device.Identifier
import com.splendo.kaluga.bluetooth.get
import com.splendo.kaluga.bluetooth.services
import com.splendo.kaluga.bluetooth.uuidString
import com.splendo.kaluga.bluetooth.value
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

class BluetoothDescriptorViewModel(private val bluetooth: Bluetooth, private val deviceIdentifier: Identifier, private val serviceUUID: UUID, private val characteristicUUID: UUID, private val descriptorUUID: UUID) : BaseViewModel() {

    private val descriptor: Flow<Descriptor?> get() = bluetooth.devices()[deviceIdentifier].services()[serviceUUID].characteristics()[characteristicUUID].descriptors()[descriptorUUID]

    val uuid = descriptorUUID.uuidString
    val value = descriptor.value().map { it?.toHexString() ?: "" }.toUninitializedObservable(coroutineScope)

    override fun onResume(scope: CoroutineScope) {
        super.onResume(scope)

        scope.launch {
            descriptor.flatMapLatest { descriptor -> descriptor?.let { flowOf(it) } ?: emptyFlow() }.first().readValue()
        }
    }

    public override fun onCleared() {
        super.onCleared()
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.bluetooth

import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType
import com.splendo.kaluga.architecture.observable.toInitializedObservable
import com.splendo.kaluga.architecture.observable.toUninitializedObservable
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.base.text.format
import com.splendo.kaluga.bluetooth.Bluetooth
import com.splendo.kaluga.bluetooth.device.DeviceState
import com.splendo.kaluga.bluetooth.device.Identifier
import com.splendo.kaluga.bluetooth.device.stringValue
import com.splendo.kaluga.bluetooth.distance
import com.splendo.kaluga.bluetooth.get
import com.splendo.kaluga.bluetooth.info
import com.splendo.kaluga.bluetooth.rssi
import com.splendo.kaluga.bluetooth.services
import com.splendo.kaluga.bluetooth.state
import com.splendo.kaluga.bluetooth.updateRssi
import com.splendo.kaluga.resources.localized
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

class DeviceDetailsSpec : NavigationBundleSpec<DeviceDetailsSpecRow<*>>(setOf(DeviceDetailsSpecRow.UUIDRow))

sealed class DeviceDetailsSpecRow<V>(associatedType: NavigationBundleSpecType<V>) : NavigationBundleSpecRow<V>(associatedType) {
    object UUIDRow : DeviceDetailsSpecRow<String>(NavigationBundleSpecType.StringType)
}

class BluetoothDeviceDetailViewModel(private val bluetooth: Bluetooth, private val identifier: Identifier) : BaseViewModel() {

    companion object {
        private const val rssi_frequency = 1000L
    }

    private val device = bluetooth.devices()[identifier]

    val name = device.info().map { it.name ?: "bluetooth_no_name".localized() }.toUninitializedObservable(coroutineScope)
    val identifierString = identifier.stringValue
    val rssi = device.rssi().map { "rssi".localized().format(it) }.toUninitializedObservable(coroutineScope)
    val distance = device.distance().map { "distance".localized().format(it) }.toUninitializedObservable(coroutineScope)
    val state = device.state().map { deviceState ->
        when (deviceState) {
            is DeviceState.Disconnecting -> "bluetooth_disconneting"
            is DeviceState.Disconnected -> "bluetooth_disconnected"
            is DeviceState.Connected.Discovering -> "bluetooth_discovering"
            is DeviceState.Connected -> "bluetooth_connected"
            is DeviceState.Connecting -> "bluetooth_connecting"
            is DeviceState.Reconnecting -> "bluetooth_reconnecting"
        }.localized()
    }.toUninitializedObservable(coroutineScope)
    private val _services = MutableStateFlow(
        emptyList<BluetoothServiceViewModel>()
    )
    val services = _services.toInitializedObservable(coroutineScope)

    override fun onResume(scope: CoroutineScope) {
        super.onResume(scope)

        scope.launch {
            device.services().map { services -> services.map { BluetoothServiceViewModel(bluetooth, identifier, it.uuid) } }.collect {
                cleanServices()
                _services.value = it
            }
        }

        scope.launch {
            while (true) {
                device.updateRssi()
                delay(rssi_frequency)
            }
        }
    }

    override fun onCleared() {
        super.onCleared()
        cleanServices()
    }

    private fun cleanServices() {
        _services.value.forEach { it.onCleared() }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.bluetooth

import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.navigation.toBundle
import com.splendo.kaluga.architecture.observable.UninitializedObservable
import com.splendo.kaluga.architecture.observable.toInitializedObservable
import com.splendo.kaluga.architecture.observable.toUninitializedObservable
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import com.splendo.kaluga.base.text.format
import com.splendo.kaluga.base.utils.toHexString
import com.splendo.kaluga.bluetooth.Bluetooth
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.connect
import com.splendo.kaluga.bluetooth.device.DeviceState
import com.splendo.kaluga.bluetooth.device.Identifier
import com.splendo.kaluga.bluetooth.device.stringValue
import com.splendo.kaluga.bluetooth.disconnect
import com.splendo.kaluga.bluetooth.get
import com.splendo.kaluga.bluetooth.state
import com.splendo.kaluga.resources.localized
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

class BluetoothListDeviceViewModel(private val identifier: Identifier, bluetooth: Bluetooth, navigator: Navigator<BluetoothListNavigation>) : NavigatingViewModel<BluetoothListNavigation>(navigator) {

    enum class ConnectButtonState {
        Connect,
        Disconnect
    }

    private val device = bluetooth.devices()[identifier]

    val name = deviceStateObservable { it.advertisementData.name ?: "bluetooth_no_name".localized() }
    val identifierString = identifier.stringValue
    val rssi = deviceStateObservable { "rssi".localized().format(it.rssi) }
    val isTxPowerVisible = deviceStateObservable { it.advertisementData.txPowerLevel != Int.MIN_VALUE }
    val txPower = deviceStateObservable { if (it.advertisementData.txPowerLevel != Int.MIN_VALUE) "txPower".localized().format(it.advertisementData.txPowerLevel) else "" }

    val isConnectButtonVisible = deviceStateObservable { it.deviceInfo.advertisementData.isConnectible }
    val connectButtonState = deviceStateObservable {
        when (it) {
            is DeviceState.Disconnected, is DeviceState.Disconnecting -> ConnectButtonState.Connect
            else -> ConnectButtonState.Disconnect
        }
    }
    val isMoreButtonVisible = deviceStateObservable { it is DeviceState.Connected }

    val status = deviceStateObservable {
        when (it) {
            is DeviceState.Disconnecting -> "bluetooth_disconneting"
            is DeviceState.Disconnected -> "bluetooth_disconnected"
            is DeviceState.Connected -> "bluetooth_connected"
            is DeviceState.Connecting -> "bluetooth_connecting"
            is DeviceState.Reconnecting -> "bluetooth_reconnecting"
        }.localized()
    }
    val serviceUUIDs = deviceStateObservable { parseServiceUUIDs(it.advertisementData.serviceUUIDs) }
    val serviceData = deviceStateObservable { parseServiceData(it.advertisementData.serviceData) }
    val manufacturerId = deviceStateObservable { "bluetooth_manufacturer_id".localized().format(it.advertisementData.manufacturerId ?: -1) }
    val manufacturerData = deviceStateObservable { "bluetooth_manufacturer_data".localized().format(it.advertisementData.manufacturerData?.toHexString() ?: "") }

    val _isFoldedOut = MutableStateFlow(false)
    val isFoldedOut = _isFoldedOut.toInitializedObservable(coroutineScope)

    private fun <T> deviceStateObservable(mapper: (DeviceState) -> T): UninitializedObservable<T> = device.state().map { mapper(it) }.toUninitializedObservable(coroutineScope)

    fun toggleFoldOut() = coroutineScope.launch {
        _isFoldedOut.value = !_isFoldedOut.value
    }

    fun onConnectPressed() = coroutineScope.launch {
        device.connect()
    }

    fun onDisconnectPressed() = coroutineScope.launch {
        device.disconnect()
    }

    fun onMorePressed() =
        navigator.navigate(
            BluetoothListNavigation(
                DeviceDetailsSpec().toBundle { specRow ->
                    when (specRow) {
                        is DeviceDetailsSpecRow.UUIDRow -> specRow.convertValue(identifier.stringValue)
                    }
                }
            )
        )

    private fun parseServiceUUIDs(uuids: List<UUID>): String {
        val uuidString = uuids.fold("") { result, next ->
            if (result.isEmpty())
                next.toString()
            else
                "$result, $next"
        }
        return "bluetooth_service_uuids".localized().format(uuidString)
    }

    private fun parseServiceData(data: Map<UUID, ByteArray?>): String {
        val dataString = data.entries.fold("") { result, next ->
            val nextString = "${next.key}: ${next.value?.toHexString() ?: ""}"
            if (result.isEmpty())
                nextString
            else
                "$result\n$nextString"
        }
        return "bluetooth_service_data".localized().format(dataString)
    }

    public override fun onCleared() {
        super.onCleared()
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.bluetooth

import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.NavigationBundle
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.observable.toInitializedObservable
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import com.splendo.kaluga.bluetooth.Bluetooth
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch

class BluetoothListNavigation(bundle: NavigationBundle<DeviceDetailsSpecRow<*>>) : NavigationAction<DeviceDetailsSpecRow<*>>(bundle)

class BluetoothListViewModel(private val bluetooth: Bluetooth, navigator: Navigator<BluetoothListNavigation>) : NavigatingViewModel<BluetoothListNavigation>(navigator) {

    private val _isScanning = MutableStateFlow(false)
    val isScanning = _isScanning.toInitializedObservable(coroutineScope)

    val title = bluetooth.isEnabled
        .mapLatest { if (it) "Enabled" else "Disabled" }
        .toInitializedObservable("Initializing...", coroutineScope)

    private val _devices = MutableStateFlow(emptyList<BluetoothListDeviceViewModel>())
    val devices = _devices.toInitializedObservable(coroutineScope)

    override fun onResume(scope: CoroutineScope) {
        super.onResume(scope)

        scope.launch { bluetooth.isScanning().collect { _isScanning.value = it } }
        scope.launch {
            bluetooth.devices().map { devices -> devices.map { BluetoothListDeviceViewModel(it.identifier, bluetooth, navigator) } }.collect { devices ->
                cleanDevices()
                _devices.value = devices.sortedByDescending { it.name.currentOrNull }
            }
        }
    }

    fun onScanPressed() {
        if (_isScanning.value) {
            bluetooth.stopScanning()
        } else {
            bluetooth.startScanning()
        }
    }

    override fun onCleared() {
        super.onCleared()
        cleanDevices()
    }

    private fun cleanDevices() {
        _devices.value.forEach { it.onCleared() }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.bluetooth

import com.splendo.kaluga.architecture.observable.toInitializedObservable
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.bluetooth.Bluetooth
import com.splendo.kaluga.bluetooth.Service
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.characteristics
import com.splendo.kaluga.bluetooth.device.Identifier
import com.splendo.kaluga.bluetooth.get
import com.splendo.kaluga.bluetooth.services
import com.splendo.kaluga.bluetooth.uuidString
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

class BluetoothServiceViewModel(private val bluetooth: Bluetooth, private val deviceIdentifier: Identifier, private val serviceUUID: UUID) : BaseViewModel() {

    private val service: Flow<Service?> get() = bluetooth.devices()[deviceIdentifier].services()[serviceUUID]

    val uuid = serviceUUID.uuidString
    private val _characteristics = MutableStateFlow(emptyList<BluetoothCharacteristicViewModel>())
    val characteristics = _characteristics.toInitializedObservable(coroutineScope)

    override fun onResume(scope: CoroutineScope) {
        super.onResume(scope)

        scope.launch {
            service.characteristics().map { characteristics -> characteristics.map { BluetoothCharacteristicViewModel(bluetooth, deviceIdentifier, serviceUUID, it.uuid) } }.collect {
                cleanCharacteristics()
                _characteristics.value = it
            }
        }
    }

    public override fun onCleared() {
        super.onCleared()
        cleanCharacteristics()
    }

    private fun cleanCharacteristics() {
        _characteristics.value.forEach { it.onCleared() }
    }
}

package com.splendo.kaluga.example.shared.viewmodel.datetimepicker

import com.splendo.kaluga.architecture.observable.toUninitializedObservable
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.base.text.DateFormatStyle
import com.splendo.kaluga.base.text.DateFormatter
import com.splendo.kaluga.base.utils.Date
import com.splendo.kaluga.datetimepicker.DateTimePickerPresenter
import com.splendo.kaluga.datetimepicker.buildDatePicker
import com.splendo.kaluga.datetimepicker.buildTimePicker
import com.splendo.kaluga.resources.localized
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

class DateTimePickerViewModel(val dateTimePickerPresenterBuilder: DateTimePickerPresenter.Builder) : BaseViewModel() {

    companion object {
        private val formatter = DateFormatter.dateTimeFormat(DateFormatStyle.Long, DateFormatStyle.Long)
    }

    private val selectedDate = MutableStateFlow(
        Date.now().apply {
            second = 0
            millisecond = 0
        }
    )
    val dateLabel = selectedDate.map { formatter.format(it) }.toUninitializedObservable(coroutineScope)

    fun onSelectDatePressed() {
        coroutineScope.launch {
            dateTimePickerPresenterBuilder.buildDatePicker(this, Date.epoch(), Date.now()) {
                setSelectedDate(this@DateTimePickerViewModel.selectedDate.value)
                setCancelButtonTitle("cancel_selection".localized())
                setConfirmButtonTitle("confirm_selection".localized())
            }.show()?.let {
                selectedDate.value = it
            }
        }
    }

    fun onSelectTimePressed() {
        coroutineScope.launch {
            dateTimePickerPresenterBuilder.buildTimePicker(this) {
                setSelectedDate(this@DateTimePickerViewModel.selectedDate.value)
                setCancelButtonTitle("cancel_selection".localized())
                setConfirmButtonTitle("confirm_selection".localized())
            }.show()?.let {
                selectedDate.value = it
            }
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.featureList

import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.observable.observableOf
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import com.splendo.kaluga.resources.localized

sealed class FeatureListNavigationAction : NavigationAction<Nothing>(null) {
    object Location : FeatureListNavigationAction()
    object Permissions : FeatureListNavigationAction()
    object Alerts : FeatureListNavigationAction()
    object DateTimePicker : FeatureListNavigationAction()
    object LoadingIndicator : FeatureListNavigationAction()
    object Architecture : FeatureListNavigationAction()
    object Bluetooth : FeatureListNavigationAction()
    object Keyboard : FeatureListNavigationAction()
    object Links : FeatureListNavigationAction()
    object System : FeatureListNavigationAction()
    object Beacons : FeatureListNavigationAction()
    object Resources : FeatureListNavigationAction()
    object PlatformSpecific : FeatureListNavigationAction()
}

sealed class Feature(val title: String) {
    object Alerts : Feature("feature_alerts".localized())
    object Architecture : Feature("feature_architecture".localized())
    object Bluetooth : Feature("feature_bluetooth".localized())
    object DateTimePicker : Feature("feature_date_time_picker".localized())
    object Keyboard : Feature("feature_keyboard".localized())
    object LoadingIndicator : Feature("feature_hud".localized())
    object Location : Feature("feature_location".localized())
    object Permissions : Feature("feature_permissions".localized())
    object Links : Feature("feature_links".localized())
    object System : Feature("feature_system".localized())
    object Beacons : Feature("feature_beacons".localized())
    object Resource : Feature("feature_resources".localized())
    object PlatformSpecific : Feature("feature_platform_specific".localized())
}

class FeatureListViewModel(navigator: Navigator<FeatureListNavigationAction>) : NavigatingViewModel<FeatureListNavigationAction>(navigator) {

    val feature = observableOf(
        listOfNotNull(
            Feature.Alerts,
            Feature.Architecture,
            Feature.Bluetooth,
            Feature.DateTimePicker,
            Feature.Keyboard,
            Feature.Links,
            Feature.LoadingIndicator,
            Feature.Location,
            Feature.Permissions,
            Feature.System,
            Feature.Beacons,
            Feature.Resource,
            Feature.PlatformSpecific.takeIf { showPlatformSpecificFeatures }
        )
    )

    fun onFeaturePressed(feature: Feature) {
        navigator.navigate(
            when (feature) {
                is Feature.Alerts -> FeatureListNavigationAction.Alerts
                is Feature.Architecture -> FeatureListNavigationAction.Architecture
                is Feature.Bluetooth -> FeatureListNavigationAction.Bluetooth
                is Feature.DateTimePicker -> FeatureListNavigationAction.DateTimePicker
                is Feature.Keyboard -> FeatureListNavigationAction.Keyboard
                is Feature.Links -> FeatureListNavigationAction.Links
                is Feature.LoadingIndicator -> FeatureListNavigationAction.LoadingIndicator
                is Feature.Location -> FeatureListNavigationAction.Location
                is Feature.Permissions -> FeatureListNavigationAction.Permissions
                is Feature.System -> FeatureListNavigationAction.System
                is Feature.Beacons -> FeatureListNavigationAction.Beacons
                is Feature.Resource -> FeatureListNavigationAction.Resources
                is Feature.PlatformSpecific -> FeatureListNavigationAction.PlatformSpecific
            }
        )
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.featureList

expect val showPlatformSpecificFeatures: Boolean

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.info

import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.NavigationBundle
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.navigation.toBundle
import com.splendo.kaluga.architecture.observable.observableOf
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import com.splendo.kaluga.review.ReviewManager
import kotlinx.coroutines.launch

class DialogSpec : NavigationBundleSpec<DialogSpecRow>(setOf(DialogSpecRow.TitleRow, DialogSpecRow.MessageRow))

sealed class DialogSpecRow : NavigationBundleSpecRow<String>(NavigationBundleSpecType.StringType) {
    object TitleRow : DialogSpecRow()
    object MessageRow : DialogSpecRow()
}

class LinkSpec : NavigationBundleSpec<LinkSpecRow>(setOf(LinkSpecRow.LinkRow))

sealed class LinkSpecRow : NavigationBundleSpecRow<String>(NavigationBundleSpecType.StringType) {
    object LinkRow : LinkSpecRow()
}

class MailSpec : NavigationBundleSpec<MailSpecRow<*>>(setOf(MailSpecRow.ToRow, MailSpecRow.SubjectRow))

sealed class MailSpecRow<V>(associatedType: NavigationBundleSpecType<V>) : NavigationBundleSpecRow<V>(associatedType) {
    object ToRow : MailSpecRow<List<String>>(NavigationBundleSpecType.StringArrayType)
    object SubjectRow : MailSpecRow<String>(NavigationBundleSpecType.StringType)
}

sealed class InfoNavigation<B : NavigationBundleSpecRow<*>>(bundle: NavigationBundle<B>) : NavigationAction<B>(bundle) {

    class Dialog(bundle: NavigationBundle<DialogSpecRow>) : InfoNavigation<DialogSpecRow>(bundle)
    class Link(bundle: NavigationBundle<LinkSpecRow>) : InfoNavigation<LinkSpecRow>(bundle)
    class Mail(bundle: NavigationBundle<MailSpecRow<*>>) : InfoNavigation<MailSpecRow<*>>(bundle)
}

class InfoViewModel(
    val reviewManagerBuilder: ReviewManager.Builder,
    navigator: Navigator<InfoNavigation<*>>
) : NavigatingViewModel<InfoNavigation<*>>(navigator) {

    sealed class Button(val title: String) {
        object About : Button("About")
        object Website : Button("Kaluga.io")
        object GitHub : Button("GitHub")
        object Mail : Button("Contact")
        object Review : Button("Review")
    }

    val reviewManager = reviewManagerBuilder.create()
    val buttons = observableOf(listOf(Button.About, Button.Website, Button.GitHub, Button.Review, Button.Mail))

    fun onButtonPressed(button: Button) {
        when (button) {
            is Button.About -> InfoNavigation.Dialog(
                DialogSpec().toBundle { row ->
                    when (row) {
                        is DialogSpecRow.TitleRow -> row.convertValue("About Us")
                        is DialogSpecRow.MessageRow -> row.convertValue("Kaluga is developed by Splendo Consulting BV")
                    }
                }
            )
            is Button.Website -> InfoNavigation.Link(
                LinkSpec().toBundle { row ->
                    when (row) {
                        is LinkSpecRow.LinkRow -> row.convertValue("https://kaluga.splendo.com")
                    }
                }
            )
            is Button.GitHub -> InfoNavigation.Link(
                LinkSpec().toBundle { row ->
                    when (row) {
                        is LinkSpecRow.LinkRow -> row.convertValue("https://github.com/splendo/kaluga")
                    }
                }
            )
            is Button.Mail -> InfoNavigation.Mail(
                MailSpec().toBundle { row ->
                    when (row) {
                        is MailSpecRow.ToRow -> row.convertValue(listOf("info@splendo.com"))
                        is MailSpecRow.SubjectRow -> row.convertValue("Question about Kaluga")
                    }
                }
            )
            is Button.Review -> {
                coroutineScope.launch {
                    reviewManager.attemptToRequestReview()
                }
                null
            }
        }?.let { navigator.navigate(it) }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.keyboard

import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.keyboard.BaseKeyboardManager
import com.splendo.kaluga.keyboard.FocusHandler

class KeyboardViewModel(keyboardManagerBuilder: BaseKeyboardManager.Builder, private val editFieldFocusHandler: FocusHandler) : BaseViewModel() {

    private val keyboardManager: BaseKeyboardManager = keyboardManagerBuilder.create(coroutineScope)

    fun onShowPressed() {
        keyboardManager.show(editFieldFocusHandler)
    }

    fun onHidePressed() {
        keyboardManager.hide()
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.link

import com.splendo.kaluga.alerts.Alert
import com.splendo.kaluga.alerts.AlertPresenter
import com.splendo.kaluga.alerts.buildAlert
import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.NavigationBundle
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.navigation.toBundle
import com.splendo.kaluga.architecture.observable.observableOf
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import com.splendo.kaluga.links.LinksBuilder
import com.splendo.kaluga.resources.localized
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable

@Serializable
data class Repository(
    val name: String,
    val type: String
)

class BrowserSpec : NavigationBundleSpec<BrowserSpecRow>(setOf(BrowserSpecRow.UrlSpecRow))

sealed class BrowserNavigationActions<B : NavigationBundleSpecRow<*>>(bundle: NavigationBundle<B>?) : NavigationAction<B>(bundle) {
    class OpenWebView(bundle: NavigationBundle<BrowserSpecRow>?) : BrowserNavigationActions<BrowserSpecRow>(bundle)
}

sealed class BrowserSpecRow : NavigationBundleSpecRow<String>(NavigationBundleSpecType.StringType) {
    object UrlSpecRow : BrowserSpecRow()
}

class LinksViewModel(
    linkRepoBuilder: LinksBuilder,
    val builder: AlertPresenter.Builder,
    navigator: Navigator<BrowserNavigationActions<BrowserSpecRow>>
) : NavigatingViewModel<BrowserNavigationActions<BrowserSpecRow>>(navigator) {

    val browserButtonText = observableOf("browser_button_text".localized())
    val linksInstructions = observableOf("links_instructions".localized())

    private val linksRepo = linkRepoBuilder.create()

    fun showAlert(title: String, message: String, style: Alert.Action.Style) {
        coroutineScope.launch {
            val action = Alert.Action("Ok", style)
            val alert = builder.buildAlert(this) {
                setTitle(title)
                setMessage(message)
                addActions(action)
            }
            alert.show()
        }
    }

    fun openWebPage() {
        val result = linksRepo.validateLink("https://kaluga-links.web.app")
        if (result != null) {
            navigator.navigate(
                BrowserNavigationActions.OpenWebView(
                    BrowserSpec().toBundle { row ->
                        when (row) {
                            is BrowserSpecRow.UrlSpecRow -> row.convertValue(
                                result
                            )
                        }
                    }
                )
            )
        } else {
            showAlert(
                "Error Alert",
                "URL is invalid.",
                Alert.Action.Style.NEGATIVE
            )
        }
    }

    fun handleIncomingLink(url: String) {
        val result = linksRepo.handleIncomingLink(url, Repository.serializer())
        if (result != null) {
            showAlert(
                "Alert",
                result.toString(),
                Alert.Action.Style.POSITIVE
            )
        } else {
            showAlert(
                "Error Alert",
                "Query is invalid or empty.",
                Alert.Action.Style.NEGATIVE
            )
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.location

import com.splendo.kaluga.architecture.observable.toInitializedObservable
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.location.Location
import com.splendo.kaluga.location.LocationStateRepoBuilder
import com.splendo.kaluga.location.location
import com.splendo.kaluga.permissions.location.LocationPermission
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

class LocationViewModel(permission: LocationPermission, repoBuilder: LocationStateRepoBuilder) : BaseViewModel() {

    private val locationStateRepo = repoBuilder.create(permission)

    private val _location = MutableStateFlow("")
    val location = _location.toInitializedObservable(coroutineScope)

    override fun onResume(scope: CoroutineScope) {
        scope.launch {
            locationStateRepo.flow().location().map { location ->
                when (location) {
                    is Location.KnownLocation -> "${location.latitudeDMS} ${location.longitudeDMS}"
                    is Location.UnknownLocation -> {
                        val lastKnownLocation = (location as? Location.UnknownLocation.WithLastLocation)?.let {
                            " Last Known Location: ${location.lastKnownLocation.latitudeDMS} ${location.lastKnownLocation.longitudeDMS}"
                        }

                        "Unknown Location. Reason: ${location.reason.name}${ lastKnownLocation ?: "" }"
                    }
                }
            }.collect {
                _location.value = it
            }
        }
    }

    public override fun onCleared() {
        super.onCleared()
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.permissions

import com.splendo.kaluga.architecture.observable.InitializedObservable
import com.splendo.kaluga.architecture.observable.UninitializedObservable
import com.splendo.kaluga.architecture.observable.toInitializedObservable
import com.splendo.kaluga.architecture.observable.toUninitializedObservable
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.permissions.Permission
import com.splendo.kaluga.permissions.PermissionState
import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.resources.localized
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

class PermissionViewModel(private val permissions: Permissions, private val permission: Permission) : BaseViewModel() {

    val permissionStateMessage: UninitializedObservable<String> = permissions[permission]
        .map { permissionState ->
            when (permissionState) {
                is PermissionState.Allowed -> "permission_allowed".localized()
                is PermissionState.Denied.Requestable -> "permission_requestable".localized()
                is PermissionState.Denied.Locked -> "permission_denied".localized()
                is PermissionState.Unknown -> "permission_unknown".localized()
            }
        }
        .toUninitializedObservable(coroutineScope)

    val showPermissionButton: UninitializedObservable<Boolean> = permissions[permission]
        .map { permissionState -> permissionState is PermissionState.Denied.Requestable }
        .toUninitializedObservable(coroutineScope)

    private val _requestMessage = MutableSharedFlow<String?>(0)
    val requestMessage: InitializedObservable<String?> = _requestMessage.toInitializedObservable(null, coroutineScope)

    fun requestPermission() {
        coroutineScope.launch {
            _requestMessage.emit((if (permissions.request(permission)) "permission_request_success" else "permission_request_failed").localized())
            _requestMessage.emit(null)
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.permissions

import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.navigation.toBundle
import com.splendo.kaluga.architecture.observable.observableOf
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import com.splendo.kaluga.resources.localized
import kotlinx.serialization.Serializable

@Serializable
enum class PermissionView(val title: String) {
    Bluetooth("permissions_bluetooth".localized()),
    Calendar("permissions_calendar".localized()),
    Camera("permissions_camera".localized()),
    Contacts("permissions_contacts".localized()),
    Location("permissions_location".localized()),
    Microphone("permissions_microphone".localized()),
    Notifications("permissions_notifications".localized()),
    Storage("permissions_storage".localized())
}

object PermissionNavigationBundleSpecRow : NavigationBundleSpecRow<PermissionView>(NavigationBundleSpecType.SerializedType(PermissionView.serializer()))
class PermissionNavigationBundleSpec : NavigationBundleSpec<PermissionNavigationBundleSpecRow>(setOf(PermissionNavigationBundleSpecRow))

class PermissionsListNavigationAction(permissionView: PermissionView) : NavigationAction<PermissionNavigationBundleSpecRow>(PermissionNavigationBundleSpec().toBundle { spec -> spec.convertValue(permissionView) })

class PermissionsListViewModel(navigator: Navigator<PermissionsListNavigationAction>) : NavigatingViewModel<PermissionsListNavigationAction>(navigator) {

    val permissions = observableOf(
        listOf(
            PermissionView.Bluetooth,
            PermissionView.Calendar,
            PermissionView.Camera,
            PermissionView.Contacts,
            PermissionView.Location,
            PermissionView.Microphone,
            PermissionView.Notifications,
            PermissionView.Storage
        )
    )

    fun onPermissionPressed(permissionView: PermissionView) {
        navigator.navigate(PermissionsListNavigationAction(permissionView))
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.resources

import com.splendo.kaluga.alerts.AlertPresenter
import com.splendo.kaluga.alerts.buildAlert
import com.splendo.kaluga.architecture.observable.observableOf
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.example.shared.stylable.ButtonStyles
import com.splendo.kaluga.resources.StringStyleAttribute
import com.splendo.kaluga.resources.StyledStringBuilder
import com.splendo.kaluga.resources.stylable.ButtonStyle
import com.splendo.kaluga.resources.styled
import com.splendo.kaluga.resources.view.KalugaButton
import kotlinx.coroutines.launch

class ButtonViewModel(
    styledStringBuilderProvider: StyledStringBuilder.Provider,
    val alertPresenterBuilder: AlertPresenter.Builder
) : BaseViewModel() {

    val buttons = observableOf(
        listOf(
            "Text Button".toButton(ButtonStyles.textButton),
            "Red Button".toButton(ButtonStyles.redButton),
            "Rounded Button".toButton(ButtonStyles.roundedButton),
            "Oval Button".toButton(ButtonStyles.ovalButton),
            "Stroke Button".toButton(ButtonStyles.redButtonWithStroke),
            "Rounded Stroke Button".toButton(ButtonStyles.roundedButtonWithStroke),
            "Oval Stroke Button".toButton(ButtonStyles.ovalButtonWithStroke),
            "Linear Gradient Button".toButton(ButtonStyles.linearGradientButton),
            "Radial Gradient Button".toButton(ButtonStyles.radialGradientButton),
            "Angular Gradient Button".toButton(ButtonStyles.angularGradientButton),
            "Disabled Button".toButton(ButtonStyles.redButton, false),
            listOf(
                KalugaButton.Styled(
                    "Styled Button".styled(
                        styledStringBuilderProvider,
                        ButtonStyles.textButton.getStateTextStyle(
                            isEnabled = true,
                            isPressed = false
                        ),
                        StringStyleAttribute.CharacterStyleAttribute.Strikethrough
                    ),
                    ButtonStyles.textButton
                ) {
                    showAlert("Styled Button")
                }
            )
        ).flatten()
    )

    private fun showAlert(message: String) {
        coroutineScope.launch {
            alertPresenterBuilder.buildAlert(this) {
                setTitle("Button Pressed")
                setMessage(message)
                setNeutralButton("Ok")
            }.show()
        }
    }

    private fun String.toButton(
        style: ButtonStyle,
        isEnabled: Boolean = true
    ): List<KalugaButton.Plain> = listOf(
        KalugaButton.Plain(this, style, isEnabled) {
            showAlert(this)
        }
    )
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.resources

import com.splendo.kaluga.alerts.Alert
import com.splendo.kaluga.alerts.AlertPresenter
import com.splendo.kaluga.alerts.buildActionSheet
import com.splendo.kaluga.alerts.buildAlert
import com.splendo.kaluga.architecture.observable.InitializedObservable
import com.splendo.kaluga.architecture.observable.toInitializedObservable
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.example.shared.stylable.ButtonStyles
import com.splendo.kaluga.resources.Color
import com.splendo.kaluga.resources.DefaultColors
import com.splendo.kaluga.resources.burn
import com.splendo.kaluga.resources.colorBlend
import com.splendo.kaluga.resources.colorFrom
import com.splendo.kaluga.resources.darken
import com.splendo.kaluga.resources.darkenBy
import com.splendo.kaluga.resources.difference
import com.splendo.kaluga.resources.dodge
import com.splendo.kaluga.resources.exclude
import com.splendo.kaluga.resources.hardLight
import com.splendo.kaluga.resources.hexString
import com.splendo.kaluga.resources.hue
import com.splendo.kaluga.resources.lighten
import com.splendo.kaluga.resources.lightenBy
import com.splendo.kaluga.resources.luminate
import com.splendo.kaluga.resources.multiply
import com.splendo.kaluga.resources.normal
import com.splendo.kaluga.resources.overlay
import com.splendo.kaluga.resources.saturate
import com.splendo.kaluga.resources.screen
import com.splendo.kaluga.resources.softLight
import com.splendo.kaluga.resources.stylable.BackgroundStyle
import com.splendo.kaluga.resources.view.KalugaButton
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

enum class SelectableBlendMode {
    NORMAL,
    MULTIPLY,
    SCREEN,
    OVERLAY,
    DARKEN,
    LIGHTEN,
    HARD_LIGHT,
    SOFT_LIGHT,
    DODGE,
    BURN,
    DIFFERENCE,
    EXCLUSION,
    HUE,
    SATURATION,
    LUMINATE,
    COLOR_BLEND;
}

class ColorViewModel(
    val alertPresenterBuilder: AlertPresenter.Builder
) : BaseViewModel() {

    private val backdropColor = MutableStateFlow(DefaultColors.mediumPurple)
    private val sourceColor = MutableStateFlow(DefaultColors.darkCyan)
    private val blendMode = MutableStateFlow(SelectableBlendMode.NORMAL)
    private val blendedColor = combine(backdropColor, sourceColor, blendMode) { backdrop, source, blendMode ->
        when (blendMode) {
            SelectableBlendMode.NORMAL -> backdrop normal source
            SelectableBlendMode.MULTIPLY -> backdrop multiply source
            SelectableBlendMode.SCREEN -> backdrop screen source
            SelectableBlendMode.OVERLAY -> backdrop overlay source
            SelectableBlendMode.DARKEN -> backdrop darken source
            SelectableBlendMode.LIGHTEN -> backdrop lighten source
            SelectableBlendMode.HARD_LIGHT -> backdrop hardLight source
            SelectableBlendMode.SOFT_LIGHT -> backdrop softLight source
            SelectableBlendMode.DODGE -> backdrop dodge source
            SelectableBlendMode.BURN -> backdrop burn source
            SelectableBlendMode.DIFFERENCE -> backdrop difference source
            SelectableBlendMode.EXCLUSION -> backdrop exclude source
            SelectableBlendMode.HUE -> backdrop hue source
            SelectableBlendMode.SATURATION -> backdrop saturate source
            SelectableBlendMode.LUMINATE -> backdrop luminate source
            SelectableBlendMode.COLOR_BLEND -> backdrop colorBlend source
        }
    }.stateIn(coroutineScope, SharingStarted.Lazily, DefaultColors.clear)

    val backdropColorBackground = backdropColor.backgroundStyleObservable
    val sourceColorBackground = sourceColor.backgroundStyleObservable
    val blendedColorBackground = blendedColor.backgroundStyleObservable

    val backdropText = backdropColor.map { it.hexString }.toInitializedObservable("", coroutineScope)
    val sourceText = sourceColor.map { it.hexString }.toInitializedObservable("", coroutineScope)

    val lightenBackdrops = backdropColor.lightenList()
    val darkenBackdrops = backdropColor.darkenList()
    val lightenSource = sourceColor.lightenList()
    val darkenSource = sourceColor.darkenList()
    val lightenBlended = blendedColor.lightenList()
    val darkenBlended = blendedColor.darkenList()

    private val steps = (1..10).map { it.toDouble() / 10.0 }
    private fun Flow<Color>.lightenList() = map { color ->
        steps.map {
            BackgroundStyle(
                BackgroundStyle.FillStyle.Solid(color.lightenBy(it))
            )
        }
    }.toInitializedObservable(emptyList(), coroutineScope)
    private fun Flow<Color>.darkenList() = map { color ->
        steps.map {
            BackgroundStyle(
                BackgroundStyle.FillStyle.Solid(color.darkenBy(it))
            )
        }
    }.toInitializedObservable(emptyList(), coroutineScope)

    fun submitBackdropText(backdropText: String) {
        submitColorText(backdropText) {
            backdropColor.value = it
        }
    }

    fun submitSourceText(sourceText: String) {
        submitColorText(sourceText) {
            sourceColor.value = it
        }
    }

    val blendModeButton = blendMode.map { currentlySelectedBlendMode ->
        KalugaButton.Plain(
            currentlySelectedBlendMode.name,
            ButtonStyles.redButton
        ) {
            alertPresenterBuilder.buildActionSheet(coroutineScope) {
                addActions(
                    SelectableBlendMode.values().map { selectableBlendMode ->
                        Alert.Action(selectableBlendMode.name) {
                            blendMode.value = selectableBlendMode
                        }
                    }
                )
            }.showAsync()
        }
    }.toInitializedObservable<KalugaButton>(KalugaButton.Plain("", ButtonStyles.redButton, false) {}, coroutineScope)

    val flipButton: KalugaButton = KalugaButton.Plain(
        "Flip",
        ButtonStyles.textButton
    ) {
        val sourceColor = sourceColor.value
        this.sourceColor.value = backdropColor.value
        backdropColor.value = sourceColor
    }

    private fun submitColorText(colorText: String, onColorParsed: (Color) -> Unit) {
        colorFrom(colorText)?.let { onColorParsed(it) } ?: run {
            coroutineScope.launch {
                alertPresenterBuilder.buildAlert(coroutineScope) {
                    setTitle("Invalid color")
                    setMessage("The text submitted is not a color")
                    setNeutralButton("OK")
                }.show()
            }
        }
    }

    private val Flow<Color>.backgroundStyleObservable: InitializedObservable<BackgroundStyle> get() = map {
        BackgroundStyle(
            BackgroundStyle.FillStyle.Solid(it)
        )
    }.toInitializedObservable(BackgroundStyle(BackgroundStyle.FillStyle.Solid(DefaultColors.clear)), coroutineScope)
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.resources

import com.splendo.kaluga.architecture.observable.observableOf
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.example.shared.stylable.TextStyles
import com.splendo.kaluga.resources.DefaultColors
import com.splendo.kaluga.resources.StringStyleAttribute
import com.splendo.kaluga.resources.StyledStringBuilder
import com.splendo.kaluga.resources.attributeSubstring
import com.splendo.kaluga.resources.defaultBoldFont
import com.splendo.kaluga.resources.stylable.TextAlignment
import com.splendo.kaluga.resources.styled
import com.splendo.kaluga.resources.view.KalugaLabel

class LabelViewModel(styledStringBuilderProvider: StyledStringBuilder.Provider) : BaseViewModel() {

    companion object {
        val loremIpsumParagraph0 = listOf(
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
            "Etiam et est quis lectus bibendum tempor.",
            "Interdum et malesuada fames ac ante ipsum primis in faucibus.",
            "Donec convallis metus efficitur mattis accumsan.",
            "Pellentesque efficitur elit felis, vitae dictum sem vehicula eu.",
            "In hac habitasse platea dictumst. Donec sollicitudin eleifend dui, sit amet viverra ex sollicitudin nec.",
            "Sed at faucibus orci.",
            "Vivamus ultricies purus eu nisi blandit, nec ullamcorper felis facilisis.",
            "Duis accumsan dolor dignissim massa malesuada, in blandit sapien vestibulum."
        ).joinToString(" ")
        val loremIpsumParagraph1 = listOf(
            "Duis eget leo commodo, consequat felis sed, vehicula justo.",
            "Morbi facilisis facilisis varius.",
            "Donec efficitur pretium pharetra.",
            "Praesent ut lorem commodo, efficitur lectus quis, dapibus augue.",
            "Quisque sed maximus quam.",
            "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Suspendisse ullamcorper, augue id blandit lobortis, arcu enim pellentesque felis, quis ultrices urna nibh et nibh.",
            "Donec et velit faucibus, luctus massa congue, ornare odio.",
            "Vestibulum interdum nec metus at eleifend.",
            "Mauris finibus magna eu dolor dignissim elementum at vitae enim.",
            "Nunc posuere, enim eu hendrerit volutpat, sapien lacus blandit dolor, eu faucibus nisl ex sed enim.",
            "Praesent in ligula eu nulla semper posuere sollicitudin et nunc."
        ).joinToString(" ")
        val loremIpsumParagraph2 = listOf(
            "Aenean eget quam eu diam volutpat tincidunt.",
            "Pellentesque maximus tristique mi, eget pellentesque risus volutpat et.",
            "Duis id ultrices mi.",
            "Praesent nec orci at nisl pulvinar lobortis ut sit amet purus.",
            "In id fermentum mi.",
            "Nunc vel nunc nisi.",
            "Vestibulum malesuada ultricies dui a blandit."
        ).joinToString(" ")
        val loremIpsum = listOf(loremIpsumParagraph0, loremIpsumParagraph1, loremIpsumParagraph2).joinToString("\n")
    }

    val labels = observableOf(
        listOf(
            KalugaLabel.Plain("Default Text", TextStyles.defaultText),
            KalugaLabel.Plain("Title Text", TextStyles.defaultTitle),
            KalugaLabel.Plain("Bold Text", TextStyles.defaultBoldText),
            KalugaLabel.Plain("Italic Text", TextStyles.defaultItalicText),
            KalugaLabel.Plain("Monospace Text", TextStyles.defaultMonospaceText),
            KalugaLabel.Plain("Red Text", TextStyles.redText),
            KalugaLabel.Plain("Opposite Aligned Text", TextStyles.oppositeText),
            KalugaLabel.Styled("Foreground Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, StringStyleAttribute.CharacterStyleAttribute.ForegroundColor(DefaultColors.deepSkyBlue))),
            KalugaLabel.Styled("Background Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, StringStyleAttribute.CharacterStyleAttribute.BackgroundColor(DefaultColors.deepSkyBlue))),
            KalugaLabel.Styled("Partial Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.TextStyle(TextStyles.redText), IntRange(0, 13)) })),
            KalugaLabel.Styled("Font Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.Font(defaultBoldFont, 12.0f), IntRange(0, 10)) })),
            KalugaLabel.Styled("Stroke Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.Stroke(2.0f, DefaultColors.limeGreen), IntRange(0, 12)) })),
            KalugaLabel.Styled("Subscript Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.SubScript, IntRange(0, 15)) })),
            KalugaLabel.Styled("Superscript Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.SuperScript, IntRange(0, 17)) })),
            KalugaLabel.Styled("Shadow Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.Shadow(DefaultColors.dimGray, 2.0f, 2.0f, 5.0f), IntRange(0, 12)) })),
            KalugaLabel.Styled("Kerning Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.Kerning(0.08f), IntRange(0, 13)) })),
            KalugaLabel.Styled("Underline Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.Underline, IntRange(0, 15)) })),
            KalugaLabel.Styled("Strikethrough Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.CharacterStyleAttribute.Strikethrough, IntRange(0, 20)) })),
            KalugaLabel.Styled("Link Styled text".styled(styledStringBuilderProvider, TextStyles.defaultText, { Pair(StringStyleAttribute.Link("https://kaluga.splendo.com"), IntRange(0, 10)) })),
            KalugaLabel.Styled(loremIpsum.styled(styledStringBuilderProvider, TextStyles.defaultText, StringStyleAttribute.ParagraphStyleAttribute.LineSpacing(5.0f, 10.0f, 6.0f))),
            KalugaLabel.Styled(
                loremIpsum.styled(
                    styledStringBuilderProvider,
                    TextStyles.defaultText,
                    { attributeSubstring(loremIpsumParagraph1, StringStyleAttribute.ParagraphStyleAttribute.LeadingIndent(10.0f)) }
                ),
            ),
            KalugaLabel.Styled(
                loremIpsum.styled(
                    styledStringBuilderProvider,
                    TextStyles.defaultText,
                    { attributeSubstring(loremIpsumParagraph1, StringStyleAttribute.ParagraphStyleAttribute.Alignment(TextAlignment.END)) }
                ),
            ),
            KalugaLabel.Styled(
                loremIpsum.styled(
                    styledStringBuilderProvider,
                    TextStyles.defaultText,
                    { attributeSubstring(this, StringStyleAttribute.ParagraphStyleAttribute.LineSpacing(2.0f, 4.0f, 1.0f)) },
                    { attributeSubstring(loremIpsumParagraph0, StringStyleAttribute.CharacterStyleAttribute.BackgroundColor(DefaultColors.deepSkyBlue)) },
                    { attributeSubstring(loremIpsumParagraph1, StringStyleAttribute.ParagraphStyleAttribute.LeadingIndent(10.0f)) },
                    { attributeSubstring(loremIpsumParagraph1, StringStyleAttribute.ParagraphStyleAttribute.Alignment(TextAlignment.END)) },
                    { attributeSubstring(loremIpsumParagraph2, StringStyleAttribute.CharacterStyleAttribute.Kerning(0.08f)) }
                ),
            )
        )
    )
}

package com.splendo.kaluga.example.shared.viewmodel.resources

import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.observable.observableOf
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import com.splendo.kaluga.resources.localized

sealed class ResourcesListNavigationAction : NavigationAction<Nothing>(null) {

    object Color : ResourcesListNavigationAction()
    object Button : ResourcesListNavigationAction()
    object Label : ResourcesListNavigationAction()
}

enum class Resource(private val titleKey: String) {
    COLOR("feature_resources_color"),
    BUTTON("feature_resources_button"),
    LABEL("feature_resources_label");

    val title: String get() = titleKey.localized()
}

class ResourcesListViewModel(navigator: Navigator<ResourcesListNavigationAction>) : NavigatingViewModel<ResourcesListNavigationAction>(navigator) {

    val resources = observableOf(Resource.values().toList())

    fun onResourceSelected(resource: Resource) {
        navigator.navigate(
            when (resource) {
                Resource.COLOR -> ResourcesListNavigationAction.Color
                Resource.BUTTON -> ResourcesListNavigationAction.Button
                Resource.LABEL -> ResourcesListNavigationAction.Label
            }
        )
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.system

import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction
import com.splendo.kaluga.architecture.observable.observableOf
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import com.splendo.kaluga.resources.localized

sealed class SystemFeatures(val name: String) {
    object Network : SystemFeatures("network_feature".localized())
}

sealed class SystemNavigationActions<Value>(
    value: Value,
    type: NavigationBundleSpecType<Value>
) : SingleValueNavigationAction<Value>(value, type) {
    object Network : SystemNavigationActions<Unit>(Unit, NavigationBundleSpecType.UnitType)
}

class SystemViewModel(
    navigator: Navigator<SystemNavigationActions<Unit>>
) : NavigatingViewModel<SystemNavigationActions<Unit>>(navigator) {

    val modules =
        observableOf(
            listOf(
                SystemFeatures.Network
            )
        )

    fun onButtonTapped(systemFeatures: SystemFeatures) {
        when (systemFeatures) {
            is SystemFeatures.Network -> navigator.navigate(SystemNavigationActions.Network)
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.system.network

import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.logging.debug
import kotlinx.cinterop.COpaquePointer
import kotlinx.cinterop.StableRef
import kotlinx.cinterop.alloc
import kotlinx.cinterop.asStableRef
import kotlinx.cinterop.nativeHeap
import kotlinx.cinterop.ptr
import kotlinx.cinterop.staticCFunction
import platform.Network.nw_interface_type_cellular
import platform.Network.nw_interface_type_wifi
import platform.Network.nw_path_get_status
import platform.Network.nw_path_is_expensive
import platform.Network.nw_path_monitor_cancel
import platform.Network.nw_path_monitor_create
import platform.Network.nw_path_monitor_set_queue
import platform.Network.nw_path_monitor_set_update_handler
import platform.Network.nw_path_monitor_start
import platform.Network.nw_path_monitor_t
import platform.Network.nw_path_monitor_update_handler_t
import platform.Network.nw_path_status_satisfied
import platform.Network.nw_path_status_unsatisfied
import platform.Network.nw_path_t
import platform.Network.nw_path_uses_interface_type
import platform.SystemConfiguration.SCNetworkReachabilityCallBack
import platform.SystemConfiguration.SCNetworkReachabilityContext
import platform.SystemConfiguration.SCNetworkReachabilityCreateWithName
import platform.SystemConfiguration.SCNetworkReachabilityFlags
import platform.SystemConfiguration.SCNetworkReachabilityFlagsVar
import platform.SystemConfiguration.SCNetworkReachabilityGetFlags
import platform.SystemConfiguration.SCNetworkReachabilityRef
import platform.SystemConfiguration.SCNetworkReachabilitySetCallback
import platform.SystemConfiguration.SCNetworkReachabilitySetDispatchQueue
import platform.SystemConfiguration.kSCNetworkReachabilityFlagsIsWWAN
import platform.SystemConfiguration.kSCNetworkReachabilityFlagsReachable
import platform.darwin.dispatch_get_main_queue

actual sealed class NetworkManager : BaseNetworkManager {

    class NWPathNetworkManager(
        override val onNetworkStateChange: NetworkStateChange,
    ) : NetworkManager() {

        private var nwPathMonitor: nw_path_monitor_t = null

        private val networkMonitor = object : nw_path_monitor_update_handler_t {
            override fun invoke(network: nw_path_t) {
                checkReachability(network)
            }
        }

        init {
            nwPathMonitor = nw_path_monitor_create()
            nw_path_monitor_set_queue(
                nwPathMonitor,
                dispatch_get_main_queue()
            )
            nw_path_monitor_set_update_handler(nwPathMonitor, networkMonitor)
            nw_path_monitor_start(nwPathMonitor)
        }

        override fun dispose() {
            nw_path_monitor_cancel(nwPathMonitor)
        }

        private fun checkReachability(network: nw_path_t) {
            when (nw_path_get_status(network)) {
                nw_path_status_satisfied -> {
                    if (nw_path_uses_interface_type(network, nw_interface_type_wifi)) {
                        if (nw_path_is_expensive(network)) {
                            // connected to hotspot
                            onNetworkStateChange(Network.Known.Wifi(isExpensive = true))
                        } else {
                            onNetworkStateChange(Network.Known.Wifi())
                        }
                    } else if (nw_path_uses_interface_type(network, nw_interface_type_cellular)) {
                        onNetworkStateChange(Network.Known.Cellular())
                    }
                }
                nw_path_status_unsatisfied -> {
                    onNetworkStateChange(Network.Known.Absent)
                }
            }
        }
    }

    class SCNetworkManager(
        override val onNetworkStateChange: NetworkStateChange
    ) : NetworkManager() {

        private var _reachability = AtomicReference<SCNetworkReachabilityRef?>(null)
        private var reachability: SCNetworkReachabilityRef?
            get() = _reachability.get()
            set(value) = _reachability.set(value)

        private val onNetworkStateChanged: SCNetworkReachabilityCallBack = staticCFunction { ref: SCNetworkReachabilityRef?, flags: SCNetworkReachabilityFlags, info: COpaquePointer? ->
            if (info == null) {
                return@staticCFunction
            }

            val networkManager = info.asStableRef<SCNetworkManager>().get()
            networkManager.checkReachability(networkManager, flags)
        }

        init {
            reachability = SCNetworkReachabilityCreateWithName(null, "www.appleiphonecell.com")

            val context = nativeHeap.alloc<SCNetworkReachabilityContext>()
            context.info = StableRef.create(this@SCNetworkManager).asCPointer()

            if (!areParametersSet(context)) {
                debug { "Something went wrong setting the parameters" }
            }

            val flag = nativeHeap.alloc<SCNetworkReachabilityFlagsVar>()
            SCNetworkReachabilityGetFlags(reachability, flag.ptr)

            nativeHeap.free(context.rawPtr)
            nativeHeap.free(flag.rawPtr)
        }

        private fun areParametersSet(context: SCNetworkReachabilityContext): Boolean {
            if (!SCNetworkReachabilitySetCallback(reachability, onNetworkStateChanged, context.ptr)) {
                return false
            }

            if (!SCNetworkReachabilitySetDispatchQueue(reachability, dispatch_get_main_queue())) {
                return false
            }
            return true
        }

        private fun checkReachability(scNetworkManager: SCNetworkManager, flags: SCNetworkReachabilityFlags) {
            when (flags) {
                kSCNetworkReachabilityFlagsReachable -> {
                    if (flags == kSCNetworkReachabilityFlagsIsWWAN) {
                        scNetworkManager.onNetworkStateChange(Network.Known.Cellular())
                    } else {
                        scNetworkManager.onNetworkStateChange(Network.Known.Wifi())
                    }
                }
                else -> {
                    scNetworkManager.onNetworkStateChange(Network.Known.Absent)
                }
            }
        }

        override fun dispose() {
            reachability = null
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.system.network

import com.splendo.kaluga.base.IOSVersion

actual class NetworkManagerBuilder : BaseNetworkManager.Builder {

    override fun create(onNetworkStateChange: NetworkStateChange): BaseNetworkManager {
        return if (IOSVersion.systemVersion >= IOSVersion(12)) {
            NetworkManager.NWPathNetworkManager(onNetworkStateChange)
        } else {
            NetworkManager.SCNetworkManager(onNetworkStateChange)
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth.device

import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.device.BaseAdvertisementData

data class MockAdvertisementData(
    override val name: String? = null,
    override val manufacturerId: Int? = null,
    override val manufacturerData: ByteArray? = null,
    override val serviceUUIDs: List<UUID> = emptyList(),
    override val serviceData: Map<UUID, ByteArray?> = emptyMap(),
    override val txPowerLevel: Int = Int.MIN_VALUE,
) : BaseAdvertisementData {
    private val _isConnectible = AtomicReference(true)
    override var isConnectible: Boolean
        get() = _isConnectible.get()
        set(value) { _isConnectible.set(value) }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth.device

import co.touchlab.stately.collections.sharedMutableMapOf
import co.touchlab.stately.concurrency.AtomicBoolean
import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.base.utils.EmptyCompletableDeferred
import com.splendo.kaluga.base.utils.complete
import com.splendo.kaluga.base.utils.toHexString
import com.splendo.kaluga.bluetooth.asBytes
import com.splendo.kaluga.bluetooth.device.BaseDeviceConnectionManager
import com.splendo.kaluga.bluetooth.device.ConnectionSettings
import com.splendo.kaluga.bluetooth.device.DeviceAction
import com.splendo.kaluga.bluetooth.device.DeviceState
import com.splendo.kaluga.bluetooth.device.DeviceStateFlowRepo
import com.splendo.kaluga.bluetooth.device.DeviceWrapper
import com.splendo.kaluga.logging.debug
import com.splendo.kaluga.test.mock.bluetooth.MockCharacteristicWrapper
import com.splendo.kaluga.test.mock.bluetooth.MockDescriptorWrapper
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import kotlin.reflect.KClass

class MockDeviceConnectionManager(
    connectionSettings: ConnectionSettings,
    deviceWrapper: DeviceWrapper,
    stateRepo: DeviceStateFlowRepo
) : BaseDeviceConnectionManager(connectionSettings, deviceWrapper, stateRepo) {

    val connectCompleted = AtomicReference(EmptyCompletableDeferred())
    val discoverServicesCompleted = AtomicReference(EmptyCompletableDeferred())
    val disconnectCompleted = AtomicReference(EmptyCompletableDeferred())
    val readRssiCompleted = AtomicReference(EmptyCompletableDeferred())
    val performActionCompleted = AtomicReference(CompletableDeferred<DeviceAction>())
    val performActionStarted = AtomicReference(CompletableDeferred<DeviceAction>())
    private val _handledAction = MutableSharedFlow<DeviceAction>(replay = 16, onBufferOverflow = BufferOverflow.DROP_OLDEST)
    val handledAction = _handledAction.asSharedFlow()
    var willActionSucceed = AtomicBoolean(true)

    fun reset() {
        connectCompleted.set(EmptyCompletableDeferred())
        discoverServicesCompleted.set(EmptyCompletableDeferred())
        disconnectCompleted.set(EmptyCompletableDeferred())
        readRssiCompleted.set(EmptyCompletableDeferred())
        performActionCompleted.set(CompletableDeferred())
        performActionStarted.set(CompletableDeferred())
        _handledAction.resetReplayCache()
    }

    override suspend fun connect() {
        connectCompleted.get().complete()
    }

    override suspend fun discoverServices() {
        discoverServicesCompleted.get().complete()
    }

    override suspend fun disconnect() {
        disconnectCompleted.get().complete()
    }

    override suspend fun readRssi() {
        readRssiCompleted.get().complete()
    }

    var waitAfterHandlingAction: MutableMap<KClass<out DeviceAction>, EmptyCompletableDeferred> = sharedMutableMapOf()

    override suspend fun performAction(action: DeviceAction) {
        currentAction = action
        debug("Mock Action: $currentAction")
        performActionStarted.get().complete(action)

        when (action) {
            is DeviceAction.Read.Characteristic -> launch {
                handleUpdatedCharacteristic(action.characteristic.uuid, willActionSucceed.value) {
                    debug("Mock Read: ${action.characteristic.uuid} value ${action.characteristic.wrapper.value?.asBytes?.toHexString()}")
                }
                _handledAction.emit(action)
            }
            is DeviceAction.Read.Descriptor -> launch {
                handleUpdatedDescriptor(action.descriptor.uuid, willActionSucceed.value)
                _handledAction.emit(action)
            }
            is DeviceAction.Write.Characteristic -> launch {
                (action.characteristic.wrapper as MockCharacteristicWrapper).updateMockValue(action.newValue)
                handleUpdatedCharacteristic(action.characteristic.uuid, willActionSucceed.value) {
                    debug("Mock Write: ${action.characteristic.uuid} value ${action.characteristic.wrapper.value?.asBytes?.toHexString()}")
                }
                debug("Will emit write action")
                _handledAction.emit(action)
                debug("Did emit write action")
            }
            is DeviceAction.Write.Descriptor -> launch {
                (action.descriptor.wrapper as MockDescriptorWrapper).updateMockValue(action.newValue)
                handleUpdatedDescriptor(action.descriptor.uuid, willActionSucceed.value)
                _handledAction.emit(action)
            }
            is DeviceAction.Notification -> launch {
                handleCurrentActionCompleted(willActionSucceed.value)
                _handledAction.emit(action)
            }
        }

        performActionCompleted.get().complete(action)
    }

    override suspend fun handleCurrentActionCompleted(succeeded: Boolean): DeviceState {

        currentAction?.let { currentAction ->
            val wait = waitAfterHandlingAction[currentAction::class]
            wait?.await()
        }
        return super.handleCurrentActionCompleted(succeeded)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.test.mock.bluetooth.scanner

import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.base.utils.EmptyCompletableDeferred
import com.splendo.kaluga.base.utils.complete
import com.splendo.kaluga.bluetooth.BluetoothMonitor
import com.splendo.kaluga.bluetooth.UUID
import com.splendo.kaluga.bluetooth.device.ConnectionSettings
import com.splendo.kaluga.bluetooth.scanner.BaseScanner
import com.splendo.kaluga.bluetooth.scanner.EnableSensorAction
import com.splendo.kaluga.bluetooth.scanner.ScanningState
import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.state.StateRepo
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow

class MockBaseScanner(
    permissions: Permissions,
    connectionSettings: ConnectionSettings,
    autoRequestPermissions: Boolean,
    autoEnableBluetooth: Boolean,
    stateRepo: StateRepo<ScanningState, MutableStateFlow<ScanningState>>,
    override val isSupported: Boolean = true
) : BaseScanner(
    permissions,
    connectionSettings,
    autoRequestPermissions,
    autoEnableBluetooth,
    stateRepo
) {

    val scanForDevicesCompleted = AtomicReference(CompletableDeferred<Set<UUID>?>())
    val stopScanningCompleted = AtomicReference(EmptyCompletableDeferred())
    val startMonitoringPermissionsCompleted = AtomicReference(EmptyCompletableDeferred())
    val stopMonitoringPermissionsCompleted = AtomicReference(EmptyCompletableDeferred())
    val requestEnableCompleted = AtomicReference(EmptyCompletableDeferred())
    val startMonitoringSensorsCompleted = AtomicReference(EmptyCompletableDeferred())
    val stopMonitoringSensorsCompleted = AtomicReference(EmptyCompletableDeferred())

    val isEnabled = MutableStateFlow(false)

    init {
        reset()
    }

    override val bluetoothEnabledMonitor: BluetoothMonitor = object : BluetoothMonitor {
        override val isServiceEnabled: Boolean
            get() = this@MockBaseScanner.isEnabled.value
        override val isEnabled: Flow<Boolean> = this@MockBaseScanner.isEnabled

        override fun startMonitoring() {}

        override fun stopMonitoring() {}
    }

    fun reset() {
        scanForDevicesCompleted.set(CompletableDeferred())
        stopScanningCompleted.set(EmptyCompletableDeferred())
        stopMonitoringSensorsCompleted.set(EmptyCompletableDeferred())
        startMonitoringSensorsCompleted.set(EmptyCompletableDeferred())
        startMonitoringPermissionsCompleted.set(EmptyCompletableDeferred())
        stopMonitoringPermissionsCompleted.set(EmptyCompletableDeferred())
        requestEnableCompleted.set(EmptyCompletableDeferred())
    }

    override fun startMonitoringPermissions() {
        super.startMonitoringPermissions()
        startMonitoringPermissionsCompleted.get().complete()
    }

    override fun stopMonitoringPermissions() {
        super.stopMonitoringPermissions()
        stopMonitoringPermissionsCompleted.get().complete()
    }

    override fun startMonitoringSensors() {
        super.startMonitoringSensors()
        startMonitoringSensorsCompleted.get().complete()
    }

    override fun stopMonitoringSensors() {
        super.stopMonitoringSensors()
        stopMonitoringSensorsCompleted.get().complete()
    }

    override suspend fun scanForDevices(filter: Set<UUID>) {
        scanForDevicesCompleted.get().complete(filter)
    }

    override suspend fun stopScanning() {
        stopScanningCompleted.get().complete()
    }

    override fun generateEnableSensorsActions(): List<EnableSensorAction> {
        return if (requestEnableCompleted.get().isCompleted) {
            emptyList()
        } else {
            listOf(
                {
                    requestEnableCompleted.get().complete()
                    true
                }
            )
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.androidtesthelper

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class AndroidTestHelperActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        println("This Activity is just here to have an entry point to run this app.")
        println("This app can be installed by instrumentation testing tools that require an application, when testing a library")
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.system.network

import com.splendo.kaluga.architecture.observable.toInitializedObservable
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.system.network.Network
import com.splendo.kaluga.system.network.state.NetworkStateRepoBuilder
import com.splendo.kaluga.system.network.state.network
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch

class NetworkViewModel(
    networkStateRepoBuilder: NetworkStateRepoBuilder
) : BaseViewModel() {

    private val networkRepo = networkStateRepoBuilder.create()

    private val _networkState: MutableStateFlow<String?> = MutableStateFlow(null)
    val networkState = _networkState.toInitializedObservable(coroutineScope)

    override fun onResume(scope: CoroutineScope) {
        super.onResume(scope)

        scope.launch {
            networkRepo.flow().network().collect {
                when (it) {
                    is Network.Unknown.WithoutLastNetwork ->
                        _networkState.value =
                            "Network's state is Unknown and without the last available connection."
                    is Network.Unknown.WithLastNetwork ->
                        _networkState.value =
                            "Network's state is Unknown and with last known connection as ${it.lastKnownNetwork}."
                    is Network.Known.Cellular ->
                        _networkState.value =
                            "Network's state is Available through Cellular."
                    is Network.Known.Wifi ->
                        _networkState.value =
                            "Network's state is Available through WIFI."
                    is Network.Known.Absent ->
                        _networkState.value =
                            "Network's state is Absent."
                }
            }
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.links

import com.splendo.kaluga.links.manager.LinksManagerBuilder

actual class LinksBuilder : Links.Builder {
    override fun create(): Links =
        Links(LinksManagerBuilder())
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.links

import com.splendo.kaluga.links.manager.LinksManager
import kotlinx.serialization.KSerializer

class Links(
    linksManagerBuilder: LinksManager.Builder
) {

    interface Builder {
        fun create(): Links
    }

    internal var linksManager: LinksManager = linksManagerBuilder.create()

    /**
     * Convert an incoming url's query into [T] and return it.
     * When the given url is invalid, it returns null.
     *
     * @param url the whole url containing the query with values.
     * @param serializer serializer of type [T].
     **/
    fun <T> handleIncomingLink(url: String, serializer: KSerializer<T>): T? {
        return linksManager.handleIncomingLink(url, serializer)
    }

    /**
     * Check the url and returns it if valid, otherwise it returns null.
     * @param url url to validate.
     **/
    fun validateLink(url: String): String? {
        return linksManager.validateLink(url)
    }
}

expect class LinksBuilder constructor() : Links.Builder

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.links

actual class LinksBuilder : Links.Builder {
    override fun create(): Links {
        TODO("Not yet implemented")
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.links

actual class LinksBuilder : Links.Builder {
    override fun create(): Links {
        TODO("Not yet implemented")
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.system.network.state

import com.splendo.kaluga.system.network.NetworkManagerBuilder

actual class NetworkStateRepoBuilder : NetworkStateRepo.Builder {
    override fun create(): NetworkStateRepo =
        NetworkStateRepo(NetworkManagerBuilder())
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import com.splendo.kaluga.architecture.observable.WithMutableState
import com.splendo.kaluga.architecture.observable.WithState

@Suppress("NOTHING_TO_INLINE")
@Composable
inline fun <R> WithState<R>.state(): State<R> =
    this.stateFlow.collectAsState()

// @Suppress("NOTHING_TO_INLINE") // inlining currently breaks compilation
@Composable
fun <R> WithMutableState<R>.mutableState(): MutableState<R> {
    val readState = state()

    return remember {
        object : MutableState<R> {
            override var value: R
                get() = readState.value
                set(value) {
                    stateFlow.value = value
                }

            override fun component1(): R = value
            override fun component2(): (R) -> Unit = {
                value = it
            }
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.datetime.timer

import com.splendo.kaluga.state.HandleAfterNewStateIsSet
import com.splendo.kaluga.state.HandleBeforeOldStateIsRemoved
import com.splendo.kaluga.state.HotStateFlowRepo
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.time.Duration
import kotlin.time.TimeSource
import com.splendo.kaluga.state.State as KalugaState

/** A coroutine delay function. */
typealias DelayFunction = suspend (Duration) -> Unit
/**
 * Timer based on the system clock.
 * @param duration timer duration
 * @param interval timer tick interval
 * @param coroutineScope a parent coroutine scope for the timer
 */
class RecurringTimer(
    override val duration: Duration,
    interval: Duration = Duration.milliseconds(100),
    timeSource: TimeSource = TimeSource.Monotonic,
    delayFunction: DelayFunction = { duration -> delay(duration) },
    coroutineScope: CoroutineScope = MainScope()
) : ControllableTimer {
    private val stateRepo = TimerStateRepo(duration, interval, timeSource, delayFunction, coroutineScope)
    override val state: StateFlow<Timer.State> = stateRepo.stateFlow

    override suspend fun start() = stateRepo.start()

    override suspend fun pause() = stateRepo.pause()
}

/** Timer state machine. */
private class TimerStateRepo(
    totalDuration: Duration,
    private val interval: Duration,
    private val timeSource: TimeSource,
    private val delayFunction: DelayFunction,
    private val coroutineScope: CoroutineScope
) : HotStateFlowRepo<TimerStateRepo.State>(
    coroutineScope.coroutineContext,
    {
        State.NotRunning.Paused(elapsedSoFar = Duration.ZERO, totalDuration = totalDuration)
    }
) {
    suspend fun start() {
        withContext(coroutineScope.coroutineContext) {
            takeAndChangeState { state ->
                when (state) {
                    is State.NotRunning.Paused -> suspend {
                        state.start(interval, timeSource, delayFunction, coroutineScope, ::finish)
                    }
                    is State.NotRunning.Finished, is State.Running -> state.remain()
                }
            }
        }
    }

    suspend fun pause() {
        withContext(coroutineScope.coroutineContext) {
            takeAndChangeState { state ->
                when (state) {
                    is State.Running -> state::pause
                    is State.NotRunning -> state.remain()
                }
            }
        }
    }

    private suspend fun finish() {
        withContext(coroutineScope.coroutineContext) {
            takeAndChangeState { state ->
                when (state) {
                    is State.Running -> state::finish
                    is State.NotRunning -> state.remain()
                }
            }
        }
    }

    /** Timer state. */
    sealed class State : KalugaState(), Timer.State {
        abstract val totalDuration: Duration
        /** Timer is not running. */
        sealed class NotRunning(protected val elapsedSoFar: Duration) : State(), Timer.State.NotRunning {
            override val elapsed = flowOf(elapsedSoFar)
            /** Timer is paused. */
            class Paused(
                elapsedSoFar: Duration,
                override val totalDuration: Duration
            ) : NotRunning(elapsedSoFar), Timer.State.NotRunning.Paused {
                fun start(
                    interval: Duration,
                    timeSource: TimeSource,
                    delayFunction: DelayFunction,
                    coroutineScope: CoroutineScope,
                    finishCallback: suspend () -> Unit
                ): Running = Running(
                    elapsedSoFar = elapsedSoFar,
                    totalDuration = totalDuration,
                    interval = interval,
                    timeSource = timeSource,
                    delayFunction = delayFunction,
                    coroutineScope = coroutineScope,
                    finishCallback = finishCallback
                )
            }

            /** Timer is finished. */
            class Finished(
                override val totalDuration: Duration
            ) : NotRunning(totalDuration), Timer.State.NotRunning.Finished
        }

        /** Timer is running. */
        class Running(
            elapsedSoFar: Duration,
            override val totalDuration: Duration,
            interval: Duration,
            timeSource: TimeSource,
            private val delayFunction: DelayFunction,
            private val coroutineScope: CoroutineScope,
            private val finishCallback: suspend () -> Unit
        ) : State(), Timer.State.Running, HandleAfterNewStateIsSet<State>, HandleBeforeOldStateIsRemoved<State> {
            override val elapsed: Flow<Duration> = tickProvider(
                offset = elapsedSoFar,
                max = totalDuration,
                interval = interval,
                timeSource = timeSource,
                delayFunction = delayFunction
            )

            private val supervisor = SupervisorJob()

            internal suspend fun pause(): NotRunning.Paused {
                return NotRunning.Paused(elapsed.first(), totalDuration)
            }
            internal fun finish(): NotRunning.Finished = NotRunning.Finished(totalDuration)

            override suspend fun beforeOldStateIsRemoved(oldState: State) {
                CoroutineScope(supervisor + coroutineScope.coroutineContext).launch {
                    delayFunction(totalDuration - elapsed.first())
                    finishCallback()
                }
            }
            override suspend fun afterNewStateIsSet(newState: State) {
                supervisor.cancelChildren()
            }
        }
    }
}

/**
 * @return flow of ticks as close to [interval] intervals as possible. starting from [offset] to the [max]
 */
private fun tickProvider(
    offset: Duration,
    max: Duration,
    interval: Duration,
    timeSource: TimeSource,
    delayFunction: suspend (Duration) -> Unit,
): Flow<Duration> {
    val mark = timeSource.markNow()

    fun newElapsed() = offset + mark.elapsedNow()

    return flow {
        var expectedElapsed = offset
        while (true) {
            // obtain and deliver a new elapsed time value
            emit(newElapsed().coerceAtMost(max))

            // obtain a new elapsed marker, as consuming
            val elapsed = newElapsed()

            // quit if the timer reached the max
            if (max <= elapsed) break

            // adaptive correction reduces the interval by time spent delivering emitted value
            var delay = interval + expectedElapsed - elapsed
            // in case consumer processed the value too long, skip to the next interval
            while (delay.isNegative()) {
                delay += interval
                expectedElapsed += interval
            }

            // wait for the next iteration
            delayFunction(delay)
            expectedElapsed += interval
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.datetime.timer

import com.splendo.kaluga.base.utils.firstInstance
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.transformWhile
import kotlin.time.Duration

/** A timer ticking a certain [duration]. */
interface Timer {
    /** Timer duration. */
    val duration: Duration
    /** Current state of the timer. */
    val state: StateFlow<State>

    sealed interface State {
        val elapsed: Flow<Duration>
        interface Running : State

        sealed interface NotRunning : State {
            interface Paused : NotRunning
            interface Finished : NotRunning
        }
    }
}

/** A timer ticking a certain [duration] with ability to [start] and [pause]. */
interface ControllableTimer : Timer {
    /** Starts the timer. */
    suspend fun start()
    /** Stops the timer. */
    suspend fun pause()
}

/** Time elapsed from the timer start. */
fun Timer.elapsed(): Flow<Duration> = state
    .transformWhile { stateValue ->
        // [Finished] is the final state, ensure the consumer's [collect] would be able to exit
        emit(stateValue)
        stateValue !is Timer.State.NotRunning.Finished
    }
    .flatMapLatest { state -> state.elapsed }

/** Awaits for the timer to finish. */
suspend fun Timer.awaitFinish() {
    state.firstInstance<Timer.State.NotRunning.Finished>()
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.example

import android.os.Bundle
import com.google.android.material.tabs.TabLayout
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.shared.viewmodel.ExampleViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel

class ExampleActivity : KalugaViewModelActivity<ExampleViewModel>(R.layout.activity_example) {

    override val viewModel: ExampleViewModel by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val tabs: TabLayout = findViewById(R.id.tabs)

        viewModel.tabs.observeInitialized { exampleTabs ->
            tabs.removeAllTabs()
            exampleTabs.forEach { tab ->
                tabs.addTab(tabs.newTab().setText(tab.title).setTag(tab))
            }
        }

        tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
            override fun onTabReselected(tab: TabLayout.Tab?) {
                val exampleTab = tab?.tag as? ExampleViewModel.Tab ?: return
                viewModel.tab.post(exampleTab)
            }

            override fun onTabSelected(tab: TabLayout.Tab?) {
                val exampleTab = tab?.tag as? ExampleViewModel.Tab ?: return
                viewModel.tab.post(exampleTab)
            }

            override fun onTabUnselected(tab: TabLayout.Tab?) { }
        })
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.example

import android.app.Application
import com.splendo.kaluga.base.ApplicationHolder
import com.splendo.kaluga.example.di.utilitiesModule
import com.splendo.kaluga.example.di.viewModelModule
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin

class ExampleApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        ApplicationHolder.application = this

        startKoin {
            androidContext(this@ExampleApplication)
            modules(utilitiesModule, viewModelModule)
        }
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.example

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment
import com.splendo.kaluga.example.databinding.ViewListButtonBinding
import com.splendo.kaluga.example.shared.viewmodel.featureList.Feature
import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel

class FeaturesListFragment : KalugaViewModelFragment<FeatureListViewModel>(R.layout.fragment_features_list) {

    override val viewModel: FeatureListViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val adapter = FeaturesAdapter(viewModel).apply {

            view.findViewById<RecyclerView>(R.id.features_list).adapter = this
        }
        viewModel.feature.observeInitialized { adapter.features = it }
    }
}

class FeaturesAdapter(private val viewModel: FeatureListViewModel) : RecyclerView.Adapter<FeaturesAdapter.FeatureViewHolder>() {

    class FeatureViewHolder(val binding: ViewListButtonBinding) : RecyclerView.ViewHolder(binding.root) {
        val button = binding.button
    }

    var features: List<Feature> = emptyList()
        set(newValue) {
            field = newValue
            notifyDataSetChanged()
        }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeatureViewHolder {
        val binding = ViewListButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return FeatureViewHolder(binding)
    }

    override fun getItemCount(): Int = features.size

    override fun onBindViewHolder(holder: FeatureViewHolder, position: Int) {
        features.getOrNull(position)?.let { feature ->
            holder.button.text = feature.title
            holder.button.setOnClickListener { viewModel.onFeaturePressed(feature) }
        } ?: run {
            holder.button.text = null
            holder.button.setOnClickListener(null)
        }
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.example

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.widget.AppCompatButton
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.RecyclerView
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment
import com.splendo.kaluga.example.shared.viewmodel.info.InfoViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel

class InfoFragment : KalugaViewModelFragment<InfoViewModel>(R.layout.fragment_info) {

    override val viewModel: InfoViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

        super.onViewCreated(view, savedInstanceState)

        val adapter = InfoAdapter(viewModel).apply {
            view.findViewById<RecyclerView>(R.id.info_buttons)
                .adapter = this
        }
        viewModel.buttons.observeInitialized { adapter.buttons = it }
    }
}

class InfoAdapter(private val viewModel: InfoViewModel) : RecyclerView.Adapter<InfoAdapter.InfoViewHolder>() {

    class InfoViewHolder(val button: AppCompatButton) : RecyclerView.ViewHolder(button)

    var buttons: List<InfoViewModel.Button> = emptyList()
        set(newValue) {
            field = newValue
            notifyDataSetChanged()
        }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InfoViewHolder {
        val button = LayoutInflater.from(parent.context).inflate(R.layout.view_list_button, parent, false) as AppCompatButton
        return InfoViewHolder(button)
    }

    override fun getItemCount(): Int = buttons.size

    override fun onBindViewHolder(holder: InfoViewHolder, position: Int) {
        buttons.getOrNull(position)?.let { button ->
            holder.button.text = button.title
            holder.button.setOnClickListener { viewModel.onButtonPressed(button) }
        } ?: run {
            holder.button.text = null
            holder.button.setOnClickListener(null)
        }
    }
}

class InfoDialog(val title: String, val message: String) : DialogFragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val v = inflater.inflate(R.layout.dialog_info, container, false)

        v.findViewById<TextView>(R.id.title).text = title
        v.findViewById<TextView>(R.id.message).text = message

        return v
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.links.manager

import android.net.UrlQuerySanitizer
import android.webkit.URLUtil

actual class PlatformLinksHandler : LinksHandler {
    override fun isValid(url: String): Boolean = URLUtil.isValidUrl(url)

    override fun extractQueryAsList(url: String): List<Any> {
        val params = UrlQuerySanitizer(url)

        if (params.parameterList == null) {
            return emptyList()
        }
        return params.parameterList.map { it.mValue }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.links.manager

import com.splendo.kaluga.links.utils.decodeFromList
import kotlinx.serialization.KSerializer

class DefaultLinksManager(
    private val linksHandler: LinksHandler
) : LinksManager {

    override fun <T> handleIncomingLink(url: String, serializer: KSerializer<T>): T? {
        val list = linksHandler.extractQueryAsList(url)
        if (list.isEmpty()) {
            return null
        }

        return decodeFromList(list, serializer)
    }

    override fun validateLink(url: String): String? {
        return if (linksHandler.isValid(url)) {
            url
        } else {
            null
        }
    }
}

class LinksManagerBuilder : LinksManager.Builder {
    override fun create(): LinksManager =
        DefaultLinksManager(PlatformLinksHandler())
}

expect class PlatformLinksHandler constructor() : LinksHandler

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.links.manager

interface LinksHandler {
    fun isValid(url: String): Boolean
    fun extractQueryAsList(url: String): List<Any>
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.links.manager

import kotlinx.serialization.KSerializer

interface LinksManager {

    interface Builder {
        fun create(): LinksManager
    }

    fun <T> handleIncomingLink(url: String, serializer: KSerializer<T>): T?
    fun validateLink(url: String): String?
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.links.utils

import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.AbstractDecoder
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule

fun <T> decodeFromList(list: List<Any>, deserializer: DeserializationStrategy<T>): T {
    val decoder = LinksDecoder(ArrayDeque(list))
    return decoder.decodeSerializableValue(deserializer)
}

class LinksDecoder(
    internal val list: ArrayDeque<Any>,
    var elementsCount: Int = 0
) : AbstractDecoder() {
    private var elementIndex = 0

    override val serializersModule: SerializersModule = EmptySerializersModule

    override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
        if (elementIndex == descriptor.elementsCount) return CompositeDecoder.DECODE_DONE
        return elementIndex++
    }

    override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
        LinksDecoder(list, descriptor.elementsCount)

    override fun decodeCollectionSize(descriptor: SerialDescriptor): Int =
        decodeInt().also {
            elementsCount = it
        }

    override fun decodeValue(): Any = list.removeFirst()

    override fun decodeInt(): Int = decodeValue().toString().toInt()

    override fun decodeBoolean(): Boolean = decodeValue().toString().toBoolean()

    override fun decodeFloat(): Float = decodeValue().toString().toFloat()

    override fun decodeDouble(): Double = decodeValue().toString().toDouble()

    override fun decodeLong(): Long = decodeValue().toString().toLong()

    override fun decodeChar(): Char = decodeValue().toString()[0]

    override fun decodeByte(): Byte = decodeValue().toString().toByte()

    override fun decodeEnum(enumDescriptor: SerialDescriptor): Int {
        val name = decodeValue().toString()
        val index = enumDescriptor.getElementIndex(name)
        if (index == CompositeDecoder.UNKNOWN_NAME) {
            throw SerializationException("${enumDescriptor.serialName} does not contain element with name '$name'")
        }

        return index
    }

    override fun decodeNotNullMark(): Boolean = decodeString() != "NULL"

    override fun decodeSequentially(): Boolean = true
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.links.manager

import platform.Foundation.NSURL
import platform.Foundation.NSURLComponents
import platform.Foundation.NSURLConnection
import platform.Foundation.NSURLQueryItem
import platform.Foundation.NSURLRequest

actual class PlatformLinksHandler : LinksHandler {
    override fun isValid(url: String): Boolean {
        val _url = NSURL.URLWithString(url) ?: return false
        return NSURLConnection.canHandleRequest(NSURLRequest.requestWithURL(_url))
    }

    override fun extractQueryAsList(url: String): List<Any> {
        val url = NSURLComponents(url)
        val queryItems = url.queryItems as List<NSURLQueryItem>? ?: return emptyList()

        return queryItems.mapNotNull { it.value }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.links.manager

actual class PlatformLinksHandler : LinksHandler {
    override fun isValid(url: String): Boolean {
        TODO("Not yet implemented")
    }

    override fun extractQueryAsList(url: String): List<Any> {
        TODO("Not yet implemented")
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.system.network

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import androidx.annotation.RequiresApi

actual sealed class NetworkManager(
    protected val context: Context
) : BaseNetworkManager {

    abstract fun determineNetworkType(): Network

    protected val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    @RequiresApi(Build.VERSION_CODES.N)
    class AndroidConnectivityCallbackManager(
        override val onNetworkStateChange: NetworkStateChange,
        context: Context
    ) : NetworkManager(context) {

        private val networkHandler = object : ConnectivityManager.NetworkCallback() {

            override fun onAvailable(network: android.net.Network) {
                super.onAvailable(network)
                val networkType = determineNetworkType()

                onNetworkStateChange(networkType)
            }

            override fun onUnavailable() {
                super.onUnavailable()
                onNetworkStateChange(Network.Known.Absent)
            }

            override fun onLosing(network: android.net.Network, maxMsToLive: Int) {
                super.onLosing(network, maxMsToLive)
                onNetworkStateChange(
                    Network.Unknown.WithoutLastNetwork(
                        Network.Unknown.Reason.LOSING
                    )
                )
            }

            override fun onLost(network: android.net.Network) {
                super.onLost(network)
                onNetworkStateChange(Network.Known.Absent)
            }
        }

        init {
            connectivityManager.registerDefaultNetworkCallback(networkHandler)
        }

        override fun determineNetworkType(): com.splendo.kaluga.system.network.Network {
            val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
            val isCellularDataEnabled = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ?: false
            val isWifiEnabled = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ?: false

            return when {
                isWifiEnabled -> {
                    Network.Known.Wifi()
                }
                isCellularDataEnabled -> {
                    Network.Known.Cellular()
                }
                else -> {
                    Network.Known.Absent
                }
            }
        }

        override fun dispose() {
            connectivityManager.unregisterNetworkCallback(networkHandler)
        }
    }

    class AndroidConnectivityReceiverManager(
        override val onNetworkStateChange: NetworkStateChange,
        context: Context
    ) : NetworkManager(context) {

        private val networkHandler = object : BroadcastReceiver() {
            override fun onReceive(c: Context, intent: Intent) {
                val networkInfo = connectivityManager.activeNetworkInfo
                if (networkInfo?.isConnectedOrConnecting == true) {
                    val networkType = determineNetworkType()
                    onNetworkStateChange(networkType)
                } else {
                    onNetworkStateChange(Network.Known.Absent)
                }
            }
        }

        init {
            context.registerReceiver(networkHandler, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
        }

        override fun determineNetworkType(): Network {
            val isMetered = connectivityManager.isActiveNetworkMetered
            return when {
                (!isMetered && connectivityManager.isDefaultNetworkActive) -> Network.Known.Wifi()
                isMetered -> Network.Known.Cellular()
                else -> Network.Known.Absent
            }
        }

        override fun dispose() {
            context.unregisterReceiver(networkHandler)
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.system.network

import android.content.Context
import android.os.Build
import com.splendo.kaluga.base.ApplicationHolder

actual class NetworkManagerBuilder(
    private val context: Context = ApplicationHolder.applicationContext
) : BaseNetworkManager.Builder {

    override fun create(onNetworkStateChange: NetworkStateChange): BaseNetworkManager {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            NetworkManager.AndroidConnectivityCallbackManager(onNetworkStateChange, context)
        } else {
            NetworkManager.AndroidConnectivityReceiverManager(onNetworkStateChange, context)
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.system.network

typealias NetworkStateChange = (Network) -> Unit

interface BaseNetworkManager {
    val onNetworkStateChange: NetworkStateChange

    interface Builder {
        fun create(onNetworkStateChange: NetworkStateChange): BaseNetworkManager
    }

    fun dispose()
}

expect class NetworkManagerBuilder
expect sealed class NetworkManager

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.system.network

interface ReachableNetworkConnection {
    val isExpensive: Boolean
}

sealed class Network {
    sealed class Unknown(open val reason: Reason) : Network() {
        enum class Reason {
            CONNECTING,
            LOSING,
            NOT_CLEAR
        }
        class WithoutLastNetwork(override val reason: Reason) : Unknown(reason)
        class WithLastNetwork(
            val lastKnownNetwork: Network.Known,
            override val reason: Reason
        ) : Unknown(reason)
    }
    sealed class Known : Network() {
        data class Cellular(override val isExpensive: Boolean = true) : Known(), ReachableNetworkConnection
        data class Wifi(override val isExpensive: Boolean = false) : Known(), ReachableNetworkConnection
        object Absent : Known()
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.system.network

actual class NetworkManagerBuilder : BaseNetworkManager.Builder {
    override fun create(onNetworkStateChange: NetworkStateChange): BaseNetworkManager {
        TODO("Not yet implemented")
    }
}

actual sealed class NetworkManager

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.compose.navigation

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscriber
import com.splendo.kaluga.architecture.navigation.ActivityNavigator
import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.NavigationSpec
import com.splendo.kaluga.architecture.navigation.Navigator

/** Routes navigation actions to underlying navigators defined by [navigatorForAction]. */
class CombinedNavigator<A : NavigationAction<*>>(
    private val navigatorForAction: CombinedNavigator.(A) -> Navigator
) : Navigator, LifecycleSubscribable by LifecycleSubscriber() {

    /** @return [ActivityNavigator] constructed with this navigation mapper.*/
    fun ((A) -> NavigationSpec).toActivityNavigator() = ActivityNavigator(this)

    override fun navigate(action: A) {
        val navigator = navigatorForAction(action)
        if (navigator is LifecycleSubscribable && navigator.manager == null) {
            // navigator depends on the lifecycle but was not subscribed yet
            manager?.let { manager ->
                with(navigator) {
                    subscribe(manager)
                    navigate(action)
                    unsubscribe()
                }
            }
        } else {
            navigator.navigate(action)
        }
    }
}

@Composable
fun <A : NavigationAction<*>> rememberCombinedNavigator(
    navigatorForAction: CombinedNavigator.(A) -> Navigator
): Navigator =
    remember { CombinedNavigator(navigatorForAction) }
        .apply { bind() }

package com.splendo.kaluga.architecture.compose.navigation

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetState
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.Navigator
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

/**
 * Route for navigating a [ModalBottomSheetLayout] using a [BottomSheetSheetContentRouteController]
 */
sealed class BottomSheetRoute {
    /**
     * Navigates within the sheet content of the [ModalBottomSheetLayout]
     * @param route The [Route] to navigate
     */
    data class SheetContent(val route: Route) : BottomSheetRoute()

    /**
     * Navigates within the content of the [ModalBottomSheetLayout]
     * @param route The [Route] to navigate
     */
    data class Content(val route: Route) : BottomSheetRoute()
}

/**
 * Converts a [Route] to a [BottomSheetRoute.SheetContent] route
 */
val Route.bottomSheetSheetContent get() = BottomSheetRoute.SheetContent(this)

/**
 * Converts a [Route] to a [BottomSheetRoute.Content] route
 */
val Route.bottomSheetContent get() = BottomSheetRoute.Content(this)

/**
 * A controller that handles the [Route] for a [ModalBottomSheetLayout]
 */
class BottomSheetRouteController(
    internal val contentRouteController: RouteController,
    internal val sheetContentRouteController: BottomSheetSheetContentRouteController
) {
    fun navigate(route: BottomSheetRoute) = when (route) {
        is BottomSheetRoute.SheetContent -> sheetContentRouteController.navigate(route.route)
        is BottomSheetRoute.Content -> contentRouteController.navigate(route.route)
    }
}

/**
 * A [RouteController] used for navigating the sheet content in a [ModalBottomSheetLayout]
 * @param navHostController The [NavHostController] to navigate the sheet content
 * @param sheetState The [ModalBottomSheetState] associated with the [ModalBottomSheetLayout]
 * @param coroutineScope A [CoroutineScope] used for navigation actions
 */
class BottomSheetSheetContentRouteController(
    internal val navHostController: NavHostController,
    internal val sheetState: ModalBottomSheetState,
    private val coroutineScope: CoroutineScope
) : RouteController {

    private val sheetContentRouteController = NavHostRouteController(navHostController, this)

    override fun navigate(newRoute: Route) {
        if (!sheetState.isVisible) {
            coroutineScope.launch {
                sheetState.animateTo(ModalBottomSheetValue.Expanded)
            }
        }
        sheetContentRouteController.navigate(newRoute)
    }

    override fun back(): Boolean = navHostController.popBackStack() || kotlin.run {
        close()
        true
    }

    override fun close() {
        coroutineScope.launch {
            sheetState.hide()
        }
    }
}

/**
 * A [Navigator] for a [ModalBottomSheetLayout]
 * @param routeController The [BottomSheetRouteController] used for navigating.
 * @param navigationMapper A mapper that converts an [NavigationAction] handled by this navigator into a [BottomSheetRoute]
 */
class ModalBottomSheetNavigator<A : NavigationAction<*>>(
    internal val routeController: BottomSheetRouteController,
    private val navigationMapper: (A) -> BottomSheetRoute
) : Navigator<A> {

    constructor(
        contentNavHostController: NavHostController,
        sheetContentNavHostController: NavHostController,
        sheetState: ModalBottomSheetState,
        coroutineScope: CoroutineScope,
        navigationMapper: (A) -> BottomSheetRoute
    ) : this(
        BottomSheetRouteController(
            NavHostRouteController(contentNavHostController),
            BottomSheetSheetContentRouteController(
                sheetContentNavHostController,
                sheetState,
                coroutineScope
            )
        ),
        navigationMapper
    )

    constructor(
        contentRouteController: RouteController,
        sheetContentRouteController: BottomSheetSheetContentRouteController,
        navigationMapper: (A) -> BottomSheetRoute
    ) : this(
        BottomSheetRouteController(contentRouteController, sheetContentRouteController),
        navigationMapper
    )

    override fun navigate(action: A) = routeController.navigate(navigationMapper(action))
}

typealias BottomSheetRootContentBuilder = (contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) -> Unit
typealias BottomSheetContentBuilder = NavGraphBuilder.(contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) -> Unit

/**
 * Creates a [ModalBottomSheetLayout] that supports navigation using a [ModalBottomSheetNavigator]
 * @param sheetContent The [BottomSheetContentBuilder] for building the navigation sheet content of the [ModalBottomSheetLayout]
 * @param contentRoot The [BottomSheetRootContentBuilder] for building the root view of the content of the [ModalBottomSheetLayout]
 * @param content The [BottomSheetContentBuilder] for building the navigation content of the [ModalBottomSheetLayout]
 */
@Composable
fun <A : NavigationAction<*>> ModalBottomSheetNavigator<A>.NavigatingModalBottomSheetLayout(
    sheetContent: BottomSheetContentBuilder,
    contentRoot: @Composable BottomSheetRootContentBuilder,
    content: BottomSheetContentBuilder
) = routeController.NavigatingModalBottomSheetLayout(
    sheetContent = sheetContent,
    contentRoot = contentRoot,
    content = content
)

/**
 * Creates a [ModalBottomSheetLayout] that supports navigation using a [ModalBottomSheetNavigator]
 * @param sheetContent The [BottomSheetContentBuilder] for building the navigation sheet content of the [ModalBottomSheetLayout]
 * @param contentRoot The [BottomSheetRootContentBuilder] for building the root view of the content of the [ModalBottomSheetLayout]
 * @param content The [BottomSheetContentBuilder] for building the navigation content of the [ModalBottomSheetLayout]
 */
@Composable
fun BottomSheetRouteController.NavigatingModalBottomSheetLayout(
    sheetContent: BottomSheetContentBuilder,
    contentRoot: @Composable BottomSheetRootContentBuilder,
    content: BottomSheetContentBuilder
) = ModalBottomSheetLayout(
    sheetContent = {
        if (sheetContentRouteController.sheetState.isVisible) {
            HardwareBackButtonNavigation(onBackButtonClickHandler = sheetContentRouteController::close)
        }
        Box(Modifier.defaultMinSize(minHeight = 1.dp)) {
            sheetContentRouteController.SetupNavHost { sheetContentNavHostController ->
                sheetContent(
                    contentRouteController.navHostController,
                    sheetContentNavHostController,
                    sheetContentRouteController.sheetState
                )
            }
        }
    },
    sheetState = sheetContentRouteController.sheetState,
) {
    contentRouteController.SetupNavHost(
        rootView = { navHostController ->
            contentRoot(
                navHostController,
                sheetContentRouteController.navHostController,
                sheetContentRouteController.sheetState
            )
        }
    ) { navHostController ->
        content(
            navHostController,
            sheetContentRouteController.navHostController,
            sheetContentRouteController.sheetState
        )
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.compose.navigation

import android.os.Bundle
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.compose.navArgument
import com.splendo.kaluga.architecture.navigation.BundleConversionError
import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.NavigationBundle
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType
import com.splendo.kaluga.architecture.navigation.NavigationBundleValue
import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction
import com.splendo.kaluga.architecture.navigation.SingleValueNavigationSpec
import com.splendo.kaluga.architecture.navigation.toBundle
import com.splendo.kaluga.base.text.DateFormatter
import com.splendo.kaluga.base.text.iso8601Pattern
import com.splendo.kaluga.base.utils.Date
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.BooleanArraySerializer
import kotlinx.serialization.builtins.ByteArraySerializer
import kotlinx.serialization.builtins.CharArraySerializer
import kotlinx.serialization.builtins.DoubleArraySerializer
import kotlinx.serialization.builtins.FloatArraySerializer
import kotlinx.serialization.builtins.IntArraySerializer
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.LongArraySerializer
import kotlinx.serialization.builtins.ShortArraySerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.Json
import kotlin.reflect.KClass

/**
 * Adds a [Composable] for an [Action] to the [NavGraphBuilder]
 * @param spec The [NavigationBundleSpec] to use for mapping route arguments to a [NavigationBundle]
 * @param content Creates the content by providing the [NavigationBundle] generated from the route arguments
 */
inline fun <SpecType : NavigationBundleSpecRow<*>, reified Action : NavigationAction<SpecType>> NavGraphBuilder.composable(
    spec: NavigationBundleSpec<SpecType>,
    noinline content: @Composable (NavigationBundle<SpecType>) -> Unit
) = composable(Action::class, spec, content)

/**
 * Adds a [Composable] for an [Action] to the [NavGraphBuilder]
 * @param actionClass The [KClass] of the [Action] for which the [Composable] should be created
 * @param spec The [NavigationBundleSpec] to use for mapping route arguments to a [NavigationBundle]
 * @param content Creates the content by providing the [NavigationBundle] generated from the route arguments
 */
fun <SpecType : NavigationBundleSpecRow<*>, Action : NavigationAction<SpecType>> NavGraphBuilder.composable(
    actionClass: KClass<Action>,
    spec: NavigationBundleSpec<SpecType>,
    content: @Composable (NavigationBundle<SpecType>) -> Unit
) {
    composable(
        route(actionClass, spec),
        arguments = spec.rows.mapNotNull {
            if (it.associatedType is NavigationBundleSpecType.OptionalType<*>) {
                navArgument(it.argumentKey) { nullable = true }
            } else {
                null
            }
        }
    ) { backStackEntry ->
        backStackEntry.arguments?.composable(spec)?.let { content(it) }
            ?: Spacer(modifier = Modifier.fillMaxSize())
    }
}

/**
 * Adds a [Composable] for an [Action] to the [NavGraphBuilder]
 * @param type The [NavigationBundleSpecType] to use for extracting the [Value] from the route arguments
 * @param content Creates the content by providing the [Value] generated from the route arguments
 */
inline fun <Value, reified Action : SingleValueNavigationAction<Value>> NavGraphBuilder.composable(
    type: NavigationBundleSpecType<Value>,
    noinline content: @Composable (Value) -> Unit
) = composable(Action::class, type, content)

@JvmName("singleValueUnitComposable")
inline fun <reified Action : SingleValueNavigationAction<Unit>> NavGraphBuilder.composable(
    noinline content: @Composable () -> Unit
) = composable(Action::class, NavigationBundleSpecType.UnitType) { content() }

@JvmName("singleValueBooleanComposable")
inline fun <reified Action : SingleValueNavigationAction<Boolean>> NavGraphBuilder.composable(
    noinline content: @Composable (Boolean) -> Unit
) = composable(Action::class, NavigationBundleSpecType.BooleanType, content)

@JvmName("singleValueBooleanArrayComposable")
inline fun <reified Action : SingleValueNavigationAction<BooleanArray>> NavGraphBuilder.composable(
    noinline content: @Composable (BooleanArray) -> Unit
) = composable(Action::class, NavigationBundleSpecType.BooleanArrayType, content)

@JvmName("singleValueByteComposable")
inline fun <reified Action : SingleValueNavigationAction<Byte>> NavGraphBuilder.composable(
    noinline content: @Composable (Byte) -> Unit
) = composable(Action::class, NavigationBundleSpecType.ByteType, content)

@JvmName("singleValueByteArrayComposable")
inline fun <reified Action : SingleValueNavigationAction<ByteArray>> NavGraphBuilder.composable(
    noinline content: @Composable (ByteArray) -> Unit
) = composable(Action::class, NavigationBundleSpecType.ByteArrayType, content)

@JvmName("singleValueCharComposable")
inline fun <reified Action : SingleValueNavigationAction<Char>> NavGraphBuilder.composable(
    noinline content: @Composable (Char) -> Unit
) = composable(Action::class, NavigationBundleSpecType.CharType, content)

@JvmName("singleValueCharArrayComposable")
inline fun <reified Action : SingleValueNavigationAction<CharArray>> NavGraphBuilder.composable(
    noinline content: @Composable (CharArray) -> Unit
) = composable(Action::class, NavigationBundleSpecType.CharArrayType, content)

@JvmName("singleValueCharSequenceComposable")
inline fun <reified Action : SingleValueNavigationAction<CharSequence>> NavGraphBuilder.composable(
    noinline content: @Composable (CharSequence) -> Unit
) = composable(Action::class, NavigationBundleSpecType.CharSequenceType, content)

@JvmName("singleValueDoubleComposable")
inline fun <reified Action : SingleValueNavigationAction<Double>> NavGraphBuilder.composable(
    noinline content: @Composable (Double) -> Unit
) = composable(Action::class, NavigationBundleSpecType.DoubleType, content)

@JvmName("singleValueDoubleArrayComposable")
inline fun <reified Action : SingleValueNavigationAction<DoubleArray>> NavGraphBuilder.composable(
    noinline content: @Composable (DoubleArray) -> Unit
) = composable(Action::class, NavigationBundleSpecType.DoubleArrayType, content)

@JvmName("singleValueFloatComposable")
inline fun <reified Action : SingleValueNavigationAction<Float>> NavGraphBuilder.composable(
    noinline content: @Composable (Float) -> Unit
) = composable(Action::class, NavigationBundleSpecType.FloatType, content)

@JvmName("singleValueFloatArrayComposable")
inline fun <reified Action : SingleValueNavigationAction<FloatArray>> NavGraphBuilder.composable(
    noinline content: @Composable (FloatArray) -> Unit
) = composable(Action::class, NavigationBundleSpecType.FloatArrayType, content)

@JvmName("singleValueIntComposable")
inline fun <reified Action : SingleValueNavigationAction<Int>> NavGraphBuilder.composable(
    noinline content: @Composable (Int) -> Unit
) = composable(Action::class, NavigationBundleSpecType.IntegerType, content)

@JvmName("singleValueIntArrayComposable")
inline fun <reified Action : SingleValueNavigationAction<IntArray>> NavGraphBuilder.composable(
    noinline content: @Composable (IntArray) -> Unit
) = composable(Action::class, NavigationBundleSpecType.IntegerArrayType, content)

@JvmName("singleValueLongComposable")
inline fun <reified Action : SingleValueNavigationAction<Long>> NavGraphBuilder.composable(
    noinline content: @Composable (Long) -> Unit
) = composable(Action::class, NavigationBundleSpecType.LongType, content)

@JvmName("singleValueLongArrayComposable")
inline fun <reified Action : SingleValueNavigationAction<LongArray>> NavGraphBuilder.composable(
    noinline content: @Composable (LongArray) -> Unit
) = composable(Action::class, NavigationBundleSpecType.LongArrayType, content)

@JvmName("singleValueShortComposable")
inline fun <reified Action : SingleValueNavigationAction<Short>> NavGraphBuilder.composable(
    noinline content: @Composable (Short) -> Unit
) = composable(Action::class, NavigationBundleSpecType.ShortType, content)

@JvmName("singleValueShortArrayComposable")
inline fun <reified Action : SingleValueNavigationAction<ShortArray>> NavGraphBuilder.composable(
    noinline content: @Composable (ShortArray) -> Unit
) = composable(Action::class, NavigationBundleSpecType.ShortArrayType, content)

@JvmName("singleValueStringComposable")
inline fun <reified Action : SingleValueNavigationAction<String>> NavGraphBuilder.composable(
    noinline content: @Composable (String) -> Unit
) = composable(Action::class, NavigationBundleSpecType.StringType, content)

@JvmName("singleValueStringArrayComposable")
inline fun <reified Action : SingleValueNavigationAction<List<String>>> NavGraphBuilder.composable(
    noinline content: @Composable (List<String>) -> Unit
) = composable(Action::class, NavigationBundleSpecType.StringArrayType, content)

@JvmName("singleValueDateComposable")
inline fun <reified Action : SingleValueNavigationAction<Date>> NavGraphBuilder.composable(
    noinline content: @Composable (Date) -> Unit
) = composable(Action::class, NavigationBundleSpecType.DateType, content)

@JvmName("singleValueDateArrayComposable")
inline fun <reified Action : SingleValueNavigationAction<List<Date>>> NavGraphBuilder.composable(
    noinline content: @Composable (List<Date>) -> Unit
) = composable(Action::class, NavigationBundleSpecType.DateArrayType, content)

inline fun <Value, reified Action : SingleValueNavigationAction<Value>> NavGraphBuilder.composable(
    serializer: KSerializer<Value>,
    noinline content: @Composable (Value) -> Unit
) = composable(Action::class, NavigationBundleSpecType.SerializedType(serializer), content)

/**
 * Adds a [Composable] for an [Action] to the [NavGraphBuilder]
 * @param actionClass The [KClass] of the [Action] for which the [Composable] should be created
 * @param type The [NavigationBundleSpecType] to use for extracting the [Value] from the route arguments
 * @param content Creates the content by providing the [Value] generated from the route arguments
 */
fun <Value, Action : SingleValueNavigationAction<Value>> NavGraphBuilder.composable(
    actionClass: KClass<Action>,
    type: NavigationBundleSpecType<Value>,
    content: @Composable (Value) -> Unit
) = composable(
    actionClass,
    SingleValueNavigationSpec(
        type
    )
) { bundle ->
    content(bundle.get(type))
}

private fun <SpecType : NavigationBundleSpecRow<*>> Bundle.composable(spec: NavigationBundleSpec<SpecType>): NavigationBundle<SpecType>? {
    return try {
        spec.toBundle { type ->
            composableValue(getString(type.argumentKey), type.associatedType)
        }
    } catch (e: BundleConversionError) {
        null
    }
}

private fun Bundle.composableValue(
    value: String?,
    specType: NavigationBundleSpecType<*>
): NavigationBundleValue<*> {
    val nonNullableValue = value ?: ""
    return when (specType) {
        is NavigationBundleSpecType.UnitType -> specType.convertValue(Unit)
        is NavigationBundleSpecType.BooleanArrayType -> specType.convertValue(
            Json.decodeFromString(
                BooleanArraySerializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.BooleanType -> specType.convertValue(
            Json.decodeFromString(
                Boolean.serializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.BundleType<*> -> throw BundleConversionError() // Unsupported for now
        is NavigationBundleSpecType.ByteArrayType -> specType.convertValue(
            Json.decodeFromString(
                ByteArraySerializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.ByteType -> specType.convertValue(
            Json.decodeFromString(
                Byte.serializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.CharArrayType -> specType.convertValue(
            Json.decodeFromString(
                CharArraySerializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.CharSequenceType -> specType.convertValue(nonNullableValue)
        is NavigationBundleSpecType.CharType -> specType.convertValue(
            Json.decodeFromString(
                Char.serializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.DateArrayType -> specType.convertValue(
            Json.decodeFromString(
                ListSerializer(String.serializer()),
                nonNullableValue
            ).map {
                DateFormatter.Companion.iso8601Pattern().parse(it) ?: throw BundleConversionError()
            }
        )
        is NavigationBundleSpecType.DateType -> specType.convertValue(
            DateFormatter.Companion.iso8601Pattern().parse(nonNullableValue)
                ?: throw BundleConversionError()
        )
        is NavigationBundleSpecType.DoubleArrayType -> specType.convertValue(
            Json.decodeFromString(
                DoubleArraySerializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.DoubleType -> specType.convertValue(
            Json.decodeFromString(
                Double.serializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.FloatArrayType -> specType.convertValue(
            Json.decodeFromString(
                FloatArraySerializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.FloatType -> specType.convertValue(
            Json.decodeFromString(
                Float.serializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.IntegerArrayType -> specType.convertValue(
            Json.decodeFromString(
                IntArraySerializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.IntegerType -> specType.convertValue(
            Json.decodeFromString(
                Int.serializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.LongArrayType -> specType.convertValue(
            Json.decodeFromString(
                LongArraySerializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.LongType -> specType.convertValue(
            Json.decodeFromString(
                Long.serializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.OptionalType<*> -> value?.let {
            composableValue(
                it,
                specType.type
            )
        } ?: specType.convertValue(null)
        is NavigationBundleSpecType.SerializedType<*> -> specType.generateValue(nonNullableValue)
            ?: throw BundleConversionError()
        is NavigationBundleSpecType.ShortArrayType -> specType.convertValue(
            Json.decodeFromString(
                ShortArraySerializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.ShortType -> specType.convertValue(
            Json.decodeFromString(
                Short.serializer(),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.StringArrayType -> specType.convertValue(
            Json.decodeFromString(
                ListSerializer(String.serializer()),
                nonNullableValue
            )
        )
        is NavigationBundleSpecType.StringType -> specType.convertValue(nonNullableValue)
    }
}

package com.splendo.kaluga.architecture.compose.navigation

import androidx.activity.OnBackPressedCallback
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import com.splendo.kaluga.architecture.compose.viewModel.KalugaViewModelComposeActivity
import com.splendo.kaluga.architecture.compose.viewModel.LocalAppCompatActivity
import com.splendo.kaluga.architecture.lifecycle.LifecycleSubscribable
import com.splendo.kaluga.architecture.lifecycle.subscribe
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

/**
 * Current implementation expects [KalugaViewModelComposeActivity] to be a host since it provides [LocalAppCompatActivity].
 */
@Composable
fun LifecycleSubscribable.bind(): LifecycleSubscribable {
    LocalAppCompatActivity.current?.let { activity ->
        DisposableEffect(Unit) {
            subscribe(activity)

            onDispose {
                unsubscribe()
            }
        }
    }
    return this
}

/**
 * Adds a handler for a hardware back button
 */
@Composable
fun HardwareBackButtonNavigation(onBackButtonClickHandler: suspend () -> Unit) {
    LocalOnBackPressedDispatcherOwner.current?.let {
        val onBackPressedDispatcher = it.onBackPressedDispatcher
        val lifecycle = LocalLifecycleOwner.current.lifecycle
        val coroutineScope = rememberCoroutineScope()
        DisposableEffect(Unit) {
            val onBackPressedCallback = ComposeOnBackPressedCallback(
                coroutineScope,
                onBackButtonClickHandler
            )

            val lifecycleObserver = object : LifecycleObserver {

                @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
                fun onResume() {
                    onBackPressedCallback.remove()
                    onBackPressedDispatcher.addCallback(onBackPressedCallback)
                }
            }
            lifecycle.addObserver(lifecycleObserver)

            onDispose {
                lifecycle.removeObserver(lifecycleObserver)
                onBackPressedCallback.remove()
            }
        }
    }
}

private class ComposeOnBackPressedCallback(
    private val coroutineScope: CoroutineScope,
    private val onBackButtonClickHandler: suspend () -> Unit
) : OnBackPressedCallback(true) {

    override fun handleOnBackPressed() {
        coroutineScope.launch {
            onBackButtonClickHandler()
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.compose.navigation

import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpec
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecRow
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType
import com.splendo.kaluga.architecture.navigation.NavigationBundleValue
import com.splendo.kaluga.base.text.DateFormatter
import com.splendo.kaluga.base.text.iso8601Pattern
import kotlinx.serialization.builtins.BooleanArraySerializer
import kotlinx.serialization.builtins.ByteArraySerializer
import kotlinx.serialization.builtins.CharArraySerializer
import kotlinx.serialization.builtins.DoubleArraySerializer
import kotlinx.serialization.builtins.FloatArraySerializer
import kotlinx.serialization.builtins.IntArraySerializer
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.LongArraySerializer
import kotlinx.serialization.builtins.ShortArraySerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.Json
import kotlin.reflect.KClass

/** Reserved route name for the back button navigation. */
internal const val ROOT_VIEW = "com.splendo.kaluga.architecture.compose.navigation.root_view"

/**
 * Key to be used to indicate an argument in a route that is associated with a NavigationBundleSpecRow
 */
val NavigationBundleSpecRow<*>.argumentKey: String get() = key ?: javaClass.simpleName

/**
 * Creates a route string associated with a [NavigationAction] class given its [NavigationBundleSpec].
 * The route created by this method will create placeholders for all properties of the [spec]
 * @param spec The [NavigationBundleSpec] describing the associated [NavigationAction]
 */
inline fun <SpecRow : NavigationBundleSpecRow<*>, reified Action : NavigationAction<SpecRow>> route(
    spec: NavigationBundleSpec<SpecRow>
): String = route(Action::class, spec)

/**
 * Creates a route string associated with a [NavigationAction] class given its [NavigationBundleSpec].
 * The route created by this method will create placeholders for all properties of the [spec]
 * @param actionClass The [KClass] of the [NavigationAction] to create the route for
 * @param spec The [NavigationBundleSpec] describing the associated [NavigationAction]
 */
fun <SpecRow : NavigationBundleSpecRow<*>, T : NavigationAction<SpecRow>> route(
    actionClass: KClass<T>,
    spec: NavigationBundleSpec<SpecRow>
): String {
    val arguments = spec.rows.mapNotNull { row ->
        val key = row.argumentKey
        when (row.associatedType) {
            is NavigationBundleSpecType.UnitType -> null
            is NavigationBundleSpecType.OptionalType<*> -> RouteArgument(key, "{$key}", false)
            else -> RouteArgument(key, "{$key}", true)
        }
    }
    return route(actionClass, *arguments.toTypedArray())
}

/**
 * Creates a route string for a [NavigationAction].
 * The route created by this method will will in all arguments of the action into the route
 */
fun <SpecRow : NavigationBundleSpecRow<*>> NavigationAction<SpecRow>.route(): String {
    val arguments = bundle?.values?.mapNotNull { (row, value) ->
        value.routeArgument?.let {
            RouteArgument(
                row.argumentKey,
                it,
                value !is NavigationBundleValue.OptionalValue
            )
        }
    } ?: emptyList()
    return route(this::class, *arguments.toTypedArray())
}

private data class RouteArgument(val key: String, val value: String, val isOptional: Boolean)

private fun route(
    navigationActionClass: KClass<out NavigationAction<*>>,
    vararg arguments: RouteArgument
): String {
    val allArguments = arguments.toList()
        .groupBy(keySelector = { it.isOptional }, valueTransform = { it.key to it.value })
    val requiredArguments = allArguments[true] ?: emptyList()
    val optionalArguments = allArguments[false] ?: emptyList()
    val baseRoute = navigationActionClass.simpleName!!
    val routeWithRequiredArguments = requiredArguments.takeIf(List<*>::isNotEmpty)
        ?.joinToString("/", prefix = "$baseRoute/") { it.second } ?: baseRoute
    return optionalArguments.takeIf(List<*>::isNotEmpty)
        ?.joinToString(
            "&",
            prefix = "$routeWithRequiredArguments?"
        ) { "${it.first}={${it.second}}" }
        ?: routeWithRequiredArguments
}

private val NavigationBundleValue<*>.routeArgument: String?
    get() = when (this) {
        is NavigationBundleValue.UnitValue -> null
        is NavigationBundleValue.BooleanArrayValue -> Json.encodeToString(
            BooleanArraySerializer(),
            value
        )
        is NavigationBundleValue.BooleanValue -> Json.encodeToString(Boolean.serializer(), value)
        is NavigationBundleValue.BundleValue<*> -> null // Unsupported for now
        is NavigationBundleValue.ByteArrayValue -> Json.encodeToString(ByteArraySerializer(), value)
        is NavigationBundleValue.ByteValue -> Json.encodeToString(Byte.serializer(), value)
        is NavigationBundleValue.CharArrayValue -> Json.encodeToString(CharArraySerializer(), value)
        is NavigationBundleValue.CharSequenceValue -> value.toString()
        is NavigationBundleValue.CharValue -> Json.encodeToString(Char.serializer(), value)
        is NavigationBundleValue.DateArrayValue -> Json.encodeToString(
            ListSerializer(String.serializer()),
            value.map { DateFormatter.Companion.iso8601Pattern().format(it) }
        )
        is NavigationBundleValue.DateValue -> DateFormatter.Companion.iso8601Pattern().format(value)
        is NavigationBundleValue.DoubleArrayValue -> Json.encodeToString(
            DoubleArraySerializer(),
            value
        )
        is NavigationBundleValue.DoubleValue -> Json.encodeToString(Double.serializer(), value)
        is NavigationBundleValue.FloatArrayValue -> Json.encodeToString(
            FloatArraySerializer(),
            value
        )
        is NavigationBundleValue.FloatValue -> Json.encodeToString(Float.serializer(), value)
        is NavigationBundleValue.IntegerArrayValue -> Json.encodeToString(
            IntArraySerializer(),
            value
        )
        is NavigationBundleValue.IntegerValue -> Json.encodeToString(Int.serializer(), value)
        is NavigationBundleValue.LongArrayValue -> Json.encodeToString(LongArraySerializer(), value)
        is NavigationBundleValue.LongValue -> Json.encodeToString(Long.serializer(), value)
        is NavigationBundleValue.OptionalValue<*> -> optionalValue?.routeArgument
        is NavigationBundleValue.SerializedValue<*> -> valueString
        is NavigationBundleValue.ShortArrayValue -> Json.encodeToString(
            ShortArraySerializer(),
            value
        )
        is NavigationBundleValue.ShortValue -> Json.encodeToString(Short.serializer(), value)
        is NavigationBundleValue.StringArrayValue -> Json.encodeToString(
            ListSerializer(String.serializer()),
            value
        )
        is NavigationBundleValue.StringValue -> value
    }

/**
 * Route for navigating within a [RouteController].
 */
sealed class Route {

    /**
     * Route that navigates to a new screen using a [NavigationAction]
     */
    sealed class Navigate<SpecRow : NavigationBundleSpecRow<*>, Action : NavigationAction<SpecRow>> :
        Route() {
        protected abstract val routeAction: Action

        /**
         * The string value of the route to navigate to
         */
        val route: String get() = routeAction.route()
    }

    /**
     * Route that navigates from the current screen to the screen associated with a [NavigationAction]
     * @param routeAction The [Action] associated with the screen to navigate to
     */
    data class NextRoute<SpecRow : NavigationBundleSpecRow<*>, Action : NavigationAction<SpecRow>>(
        override val routeAction: Action
    ) : Navigate<SpecRow, Action>()

    /**
     * Route that navigates from a given route to the screen associated with a [NavigationAction]
     * @param routeAction The [Action] associated with the screen to navigate to
     * @param from The route to navigate from
     */
    data class FromRoute<SpecRow : NavigationBundleSpecRow<*>, Action : NavigationAction<SpecRow>>(
        override val routeAction: Action,
        val from: String
    ) : Navigate<SpecRow, Action>()

    /**
     * Route that navigates back to the screen associated with a [NavigationAction]
     * @param routeAction The [Action] associated with the screen to navigate back to
     */
    data class PopTo<SpecRow : NavigationBundleSpecRow<*>, Action : NavigationAction<SpecRow>>(
        override val routeAction: Action
    ) : Navigate<SpecRow, Action>()

    /**
     * Route that navigates back from the screen associated with a [NavigationAction]
     * @param routeAction The [Action] associated with the screen to navigate back from
     */
    data class PopToIncluding<SpecRow : NavigationBundleSpecRow<*>, Action : NavigationAction<SpecRow>>(
        override val routeAction: Action
    ) : Navigate<SpecRow, Action>()

    /**
     * Route that replaces the current screen to the screen associated with a [NavigationAction]
     * @param routeAction The [Action] associated with the screen to replace the current screen with
     */
    data class Replace<SpecRow : NavigationBundleSpecRow<*>, Action : NavigationAction<SpecRow>>(
        override val routeAction: Action
    ) : Navigate<SpecRow, Action>()

    /**
     * Navigates back one step in the hierarchy
     */
    object Back : Route()

    /**
     * Navigates to the Root of a navigation stack
     */
    object PopToRoot : Route()

    /**
     * Closes all screens in the navigation stack
     */
    object Close : Route()
}

/**
 * Creates a [Route.NextRoute] from [Action]
 */
val <SpecRow : NavigationBundleSpecRow<*>, Action : NavigationAction<SpecRow>> Action.next
    get() = Route.NextRoute(
        this
    )

/**
 * Creates a [Route.FromRoute] from [Action]
 * @param from The string route to navigate from
 */
fun <SpecRow : NavigationBundleSpecRow<*>, Action : NavigationAction<SpecRow>> Action.from(route: String) =
    Route.FromRoute(this, route)

/**
 * Creates a [Route.FromRoute] that navigates to the Root view from [Action]
 */
val <SpecRow : NavigationBundleSpecRow<*>, Action : NavigationAction<SpecRow>> Action.fromRoot
    get() = Route.FromRoute(
        this,
        ROOT_VIEW
    )

/**
 * Creates a [Route.PopTo] from [Action]
 */
val <SpecRow : NavigationBundleSpecRow<*>, Action : NavigationAction<SpecRow>> Action.popTo
    get() = Route.PopTo(
        this
    )

/**
 * Creates a [Route.PopToIncluding] from [Action]
 */
val <SpecRow : NavigationBundleSpecRow<*>, Action : NavigationAction<SpecRow>> Action.popToIncluding
    get() = Route.PopToIncluding(
        this
    )

/**
 * Creates a [Route.Replace] from [Action]
 */
val <SpecRow : NavigationBundleSpecRow<*>, Action : NavigationAction<SpecRow>> Action.replace
    get() = Route.Replace(
        this
    )

package com.splendo.kaluga.architecture.compose.navigation

import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.Navigator

/**
 * Controller for navigating to a [Route]
 */
sealed interface RouteController {
    /**
     * Navigates to a [Route]
     * @param newRoute The [Route] to navigate to
     */
    fun navigate(newRoute: Route)

    /**
     * Navigates back
     */
    fun back(): Boolean

    /**
     * Closes this RouteController
     */
    fun close()
}

internal val RouteController.navHostController get() = when (this) {
    is NavHostRouteController -> navHostController
    is BottomSheetSheetContentRouteController -> navHostController
}

/**
 * A [RouteController] managed by a [NavHostController]
 * @param navHostController The [NavHostController] managing the route stack
 * @param parentRouteController The [NavHostController] that is a parent to the [navHostController] if it exists
 */
class NavHostRouteController(
    internal val navHostController: NavHostController,
    private val parentRouteController: RouteController? = null
) : RouteController {
    override fun navigate(newRoute: Route) {
        when (newRoute) {
            is Route.NextRoute<*, *> -> navHostController.navigate(newRoute.route) {
                launchSingleTop = true
            }
            is Route.FromRoute<*, *> -> navHostController.navigate(newRoute.route) {
                popUpTo(newRoute.from)
            }
            is Route.Replace<*, *> -> {
                navHostController.popBackStack()
                navHostController.navigate(newRoute.route)
            }
            is Route.PopTo<*, *> -> {
                navHostController.popBackStack(newRoute.route, false)
            }
            is Route.PopToIncluding<*, *> -> {
                navHostController.popBackStack(newRoute.route, true)
            }
            is Route.Back -> back()
            is Route.PopToRoot -> navHostController.popBackStack(ROOT_VIEW, false)
            is Route.Close -> close()
        }
    }

    override fun back(): Boolean = navHostController.popBackStack() || parentRouteController?.back() ?: false
    override fun close() {
        navHostController.popBackStack(ROOT_VIEW, true)
        parentRouteController?.close()
    }
}

typealias RouteRootContentBuilder = (NavHostController) -> Unit
typealias RouteContentBuilder = NavGraphBuilder.(NavHostController) -> Unit

/**
 * A Navigator managed by a [RouteController]
 * @param routeController The [RouteController] managing this navigation
 * @param navigationMapper A mapper that converts an [NavigationAction] handled by this navigator into a [Route]
 */
open class RouteNavigator<A : NavigationAction<*>>(
    internal val routeController: RouteController,
    private val navigationMapper: (A) -> Route
) : Navigator<A> {

    constructor(
        navHostController: NavHostController,
        navigationMapper: (A) -> Route
    ) : this(NavHostRouteController(navHostController), navigationMapper)

    override fun navigate(action: A) {
        routeController.navigate(navigationMapper(action))
    }
}

/**
 * Creates a a [NavHost] for a given [RouteNavigator]
 * @param builder The [RouteContentBuilder] for building the content of the [NavHost]
 */
@Composable
fun <A : NavigationAction<*>> RouteNavigator<A>.SetupNavHost(builder: RouteContentBuilder) = routeController.SetupNavHost(builder)

/**
 * Creates a a [NavHost] for a given [RouteController]
 * @param builder The [RouteContentBuilder] for building the content of the [NavHost]
 */
@Composable
fun RouteController.SetupNavHost(builder: RouteContentBuilder) {
    SetupNavHost(startDestination = ROOT_VIEW) {
        composable(ROOT_VIEW) {
            Spacer(modifier = Modifier.fillMaxWidth())
        }
        builder(navHostController)
    }
}

/**
 * Creates a a [NavHost] for a given [RouteNavigator]
 * @param rootView The [RouteRootContentBuilder] for the initial view of the [NavHost]
 * @param builder The [RouteContentBuilder] for building the content of the [NavHost]
 */
@Composable
fun <A : NavigationAction<*>> RouteNavigator<A>.SetupNavHost(
    rootView: @Composable RouteRootContentBuilder,
    builder: RouteContentBuilder
) = routeController.SetupNavHost(rootView, builder)

/**
 * Creates a a [NavHost] for a given [RouteController]
 * @param rootView The [RouteRootContentBuilder] for the initial view of the [NavHost]
 * @param builder The [RouteContentBuilder] for building the content of the [NavHost]
 */
@Composable
fun RouteController.SetupNavHost(
    rootView: @Composable RouteRootContentBuilder,
    builder: RouteContentBuilder
) {
    SetupNavHost(startDestination = ROOT_VIEW) {
        composable(ROOT_VIEW, content = { rootView(navHostController) })
        builder(navHostController)
    }
}

/**
 * Creates a a [NavHost] for a given [RouteNavigator]
 * @param startDestination The start destination of the [NavHost]
 * @param builder The [RouteContentBuilder] for building the content of the [NavHost]
 */
@Composable
fun <A : NavigationAction<*>> RouteNavigator<A>.SetupNavHost(
    startDestination: String,
    builder: NavGraphBuilder.(RouteController) -> Unit
) = routeController.SetupNavHost(startDestination, builder)

/**
 * Creates a a [NavHost] for a given [RouteController]
 * @param startDestination The start destination of the [NavHost]
 * @param builder The [RouteContentBuilder] for building the content of the [NavHost]
 */
@Composable
fun RouteController.SetupNavHost(
    startDestination: String,
    builder: NavGraphBuilder.(RouteController) -> Unit
) {
    NavHost(
        navController = navHostController,
        startDestination = startDestination,
        builder = { builder(this@SetupNavHost) }
    )
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.architecture.compose.viewModel

import android.annotation.SuppressLint
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocal
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity

/** A [CompositionLocal] containing the current activity. */
val LocalAppCompatActivity = staticCompositionLocalOf<AppCompatActivity?> { null }

/**
 * An implementation of [KalugaViewModelActivity] which provides a [CompositionLocal] containing
 * the current activity.
 */
abstract class KalugaViewModelComposeActivity<VM : BaseViewModel> : KalugaViewModelActivity<VM>() {

    @SuppressLint("MissingSuperCall") // Lint bug
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            CompositionLocalProvider(
                LocalAppCompatActivity provides this
            ) {
                Layout(viewModel)
            }
        }
    }

    @Composable
    protected abstract fun Layout(viewModel: VM)
}

package com.splendo.kaluga.architecture.compose.viewModel

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel

/**
 * Composable which manages [viewModel] lifecycle and optionally adds it to local [ViewModelStore].
 * @param viewModel view model to manage
 * @param content content based on [viewModel]
 */
@Composable
fun <ViewModel : BaseViewModel> ViewModelComposable(
    viewModel: ViewModel,
    content: @Composable (ViewModel.() -> Unit)? = null
) {
    viewModel.linkLifecycle()
    content?.invoke(viewModel)
}

/**
 * Stores a view model in the local [ViewModelStore]. Use if the view model
 * was created manually and is not located in Activity/Fragment [ViewModelStore].
 */
@Composable fun <VM : BaseViewModel> store(provider: @Composable () -> VM): VM =
    provider().also { handleLocalViewModelStore(it) }

@Composable
private fun <VM : BaseViewModel> handleLocalViewModelStore(viewModel: VM): VM {
    // we delegate VM cleanup to the ViewModelStore, which lives in scope of the current @Composable
    val viewModelStoreOwner = rememberComposableViewModelStoreOwner(viewModel)

    // ViewModelProvider is the one, who can access ViewModelStore.put()
    val viewModelProvider = remember(viewModelStoreOwner) {
        ViewModelProvider(
            viewModelStoreOwner,
            @Suppress("UNCHECKED_CAST")
            object : ViewModelProvider.Factory {
                override fun <T : androidx.lifecycle.ViewModel?> create(modelClass: Class<T>): T =
                    viewModel as T
            }
        )
    }
    // actual injection of the VM into the ViewModelStore
    viewModelProvider.get(viewModel::class.java)

    return viewModel
}

@Composable
private fun rememberComposableViewModelStoreOwner(viewModel: BaseViewModel): ViewModelStoreOwner {
    val viewModelStoreOwner = remember(viewModel) {
        val viewModelStore = ViewModelStore()
        ViewModelStoreOwner { viewModelStore }
    }
    DisposableEffect(Unit) {
        onDispose {
            viewModelStoreOwner.viewModelStore.clear()
        }
    }

    return viewModelStoreOwner
}

@Composable
private fun <VM : BaseViewModel> VM.linkLifecycle(): VM {
    val lifecycle = LocalLifecycleOwner.current.lifecycle
    DisposableEffect(Unit) {
        val observer = VmObserver(this@linkLifecycle)

        lifecycle.addObserver(observer)

        onDispose {
            lifecycle.removeObserver(observer)
            observer.onDispose()
        }
    }
    return this
}

private class VmObserver<VM : BaseViewModel>(private val viewModel: VM) : LifecycleObserver {
    private var resumed = false

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume() = viewModel.didResume().also { resumed = true }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun onPause() = viewModel.didPause().also { resumed = false }

    fun onDispose() {
        if (resumed) {
            viewModel.didPause()
        }
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.example.alerts

import android.annotation.SuppressLint
import android.os.Bundle
import androidx.appcompat.widget.AppCompatButton
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.R
import com.splendo.kaluga.example.shared.AlertViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel

@SuppressLint("SetTextI18n")
class AlertsActivity : KalugaViewModelActivity<AlertViewModel>(R.layout.activity_alerts) {

    override val viewModel: AlertViewModel by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        findViewById<AppCompatButton>(R.id.btn_simple_alert).setOnClickListener { viewModel.showAlert() }
        findViewById<AppCompatButton>(R.id.btn_dismissible_alert).setOnClickListener { viewModel.showAndDismissAfter(3) }
        findViewById<AppCompatButton>(R.id.btn_alert_list).setOnClickListener { viewModel.showAlertWithList() }
        findViewById<AppCompatButton>(R.id.btn_alert_input_field).setOnClickListener { viewModel.showAlertWithInput() }
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.example.architecture

import android.os.Bundle
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType
import com.splendo.kaluga.architecture.navigation.toTypedProperty
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.databinding.ActivityArchitectureDetailsBinding
import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel
import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf

class ArchitectureDetailsActivity : KalugaViewModelActivity<ArchitectureDetailsViewModel>() {

    companion object {
        val resultCode = 1
    }

    override val viewModel: ArchitectureDetailsViewModel by viewModel {
        val type = NavigationBundleSpecType.SerializedType(InputDetails.serializer())
        intent.extras?.toTypedProperty(type)?.let { details ->
            parametersOf(details)
        } ?: parametersOf("", 0)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityArchitectureDetailsBinding.inflate(layoutInflater, null, false)
        setContentView(binding.root)
        binding.viewModel = viewModel
        binding.lifecycleOwner = this
    }

    override fun onBackPressed() {
        viewModel.onClosePressed()
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.example.architecture

import android.content.Intent
import android.os.Bundle
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType
import com.splendo.kaluga.architecture.navigation.SingleValueNavigationSpec
import com.splendo.kaluga.architecture.navigation.toNavigationBundle
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.databinding.ActivityArchitectureInputBinding
import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureInputViewModel
import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails
import org.koin.androidx.viewmodel.ext.android.viewModel

class ArchitectureInputActivity : KalugaViewModelActivity<ArchitectureInputViewModel>() {

    companion object {
        val requestCode = 2
    }

    override val viewModel: ArchitectureInputViewModel by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityArchitectureInputBinding.inflate(layoutInflater, null, false)
        setContentView(binding.root)
        binding.viewModel = viewModel
        binding.lifecycleOwner = this
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (requestCode == ArchitectureInputActivity.requestCode && resultCode == ArchitectureDetailsActivity.resultCode) {
            val type = NavigationBundleSpecType.SerializedType(InputDetails.serializer())
            data?.extras?.toNavigationBundle(SingleValueNavigationSpec(type))?.let { bundle ->
                val inputDetails = bundle.get(type)
                viewModel.nameInput.post(inputDetails.name)
                viewModel.numberInput.post(inputDetails.number.toString())
            }
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.beacons

import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.core.view.forEach
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.R
import com.splendo.kaluga.example.databinding.ActivityBeaconsBinding
import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListViewModel
import kotlinx.coroutines.runBlocking
import org.koin.androidx.viewmodel.ext.android.viewModel

class BeaconsActivity : KalugaViewModelActivity<BeaconsListViewModel>() {

    override val viewModel: BeaconsListViewModel by viewModel()

    private lateinit var adapter: BeaconsAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityBeaconsBinding.inflate(layoutInflater, null, false)
        binding.viewModel = viewModel
        binding.lifecycleOwner = this
        setContentView(binding.root)
        adapter = BeaconsAdapter(this)
        binding.beaconsList.adapter = adapter
        binding.beaconsList.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))

        viewModel.isScanning.observe {
            invalidateOptionsMenu()
        }
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.bluetooth_menu, menu)
        return true
    }

    override fun onPrepareOptionsMenu(menu: Menu?): Boolean = runBlocking {
        val scanning = viewModel.isScanning.current
        menu?.forEach { item ->
            when (item.itemId) {
                R.id.start_scanning -> item.isVisible = !scanning
                R.id.stop_scanning -> item.isVisible = scanning
            }
        }
        super.onPrepareOptionsMenu(menu)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.start_scanning,
            R.id.stop_scanning -> {
                viewModel.onScanPressed()
                true
            }
            else -> super.onOptionsItemSelected(item)
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.beacons

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import com.splendo.kaluga.example.databinding.BeaconItemBinding
import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListBeaconViewModel

class BeaconsAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter<BeaconsAdapter.BeaconItemViewHolder>() {

    class BeaconItemViewHolder(val binding: BeaconItemBinding) : RecyclerView.ViewHolder(binding.root)

    internal var beacons: List<BeaconsListBeaconViewModel> = emptyList()
        set(value) {
            field = value
            notifyDataSetChanged()
        }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BeaconItemViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = BeaconItemBinding.inflate(inflater, parent, false)
        binding.lifecycleOwner = lifecycleOwner
        return BeaconItemViewHolder(binding)
    }

    override fun getItemCount(): Int {
        return beacons.size
    }

    override fun onBindViewHolder(holder: BeaconItemViewHolder, position: Int) {
        val viewModel = beacons[position]
        holder.binding.viewModel = viewModel
    }

    override fun onViewAttachedToWindow(holder: BeaconItemViewHolder) {
        super.onViewAttachedToWindow(holder)

        holder.binding.viewModel?.didResume()
    }

    override fun onViewDetachedFromWindow(holder: BeaconItemViewHolder) {
        super.onViewDetachedFromWindow(holder)

        holder.binding.viewModel?.didPause()
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.beacons

import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListBeaconViewModel

object BeaconsBinding {
    @BindingAdapter("beacons")
    @JvmStatic
    fun bindBeacons(view: RecyclerView, beacons: List<BeaconsListBeaconViewModel>?) {
        val beaconsAdapter = view.adapter as? BeaconsAdapter ?: return
        beaconsAdapter.beacons = beacons ?: emptyList()
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.bluetooth

import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.core.view.forEach
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.R
import com.splendo.kaluga.example.databinding.ActivityBluetoothBinding
import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListViewModel
import kotlinx.coroutines.runBlocking
import org.koin.androidx.viewmodel.ext.android.viewModel

class BluetoothActivity : KalugaViewModelActivity<BluetoothListViewModel>() {

    override val viewModel: BluetoothListViewModel by viewModel()

    private lateinit var adapter: BluetoothAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityBluetoothBinding.inflate(layoutInflater, null, false)
        binding.viewModel = viewModel
        binding.lifecycleOwner = this
        setContentView(binding.root)
        adapter = BluetoothAdapter(this)
        binding.devicesList.adapter = adapter
        binding.devicesList.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))

        viewModel.isScanning.observe {
            invalidateOptionsMenu()
        }

        viewModel.title.observe(::setTitle)
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.bluetooth_menu, menu)
        return true
    }

    override fun onPrepareOptionsMenu(menu: Menu?): Boolean = runBlocking {
        val scanning = viewModel.isScanning.current
        menu?.forEach { item ->
            when (item.itemId) {
                R.id.start_scanning -> item.isVisible = !scanning
                R.id.stop_scanning -> item.isVisible = scanning
            }
        }
        super.onPrepareOptionsMenu(menu)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.start_scanning,
            R.id.stop_scanning -> {
                viewModel.onScanPressed()
                true
            }
            else -> super.onOptionsItemSelected(item)
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.bluetooth

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.BindingAdapter
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import com.splendo.kaluga.example.databinding.BluetoothItemBinding
import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListDeviceViewModel

object DevicesBinding {

    @BindingAdapter("devices")
    @JvmStatic
    fun bindDevices(view: RecyclerView, devices: List<BluetoothListDeviceViewModel>?) {
        val bluetoothAdapter = view.adapter as? BluetoothAdapter
            ?: return
        bluetoothAdapter.bluetoothDevices = devices ?: emptyList()
    }
}

class BluetoothAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter<BluetoothAdapter.BluetoothItemViewHolder>() {

    class BluetoothItemViewHolder(val binding: BluetoothItemBinding) : RecyclerView.ViewHolder(binding.root)

    internal var bluetoothDevices: List<BluetoothListDeviceViewModel> = emptyList()
        set(value) {
            field = value
            notifyDataSetChanged()
        }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BluetoothItemViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = BluetoothItemBinding.inflate(inflater, parent, false)
        binding.lifecycleOwner = lifecycleOwner
        return BluetoothItemViewHolder(binding)
    }

    override fun getItemCount(): Int {
        return bluetoothDevices.size
    }

    override fun onBindViewHolder(holder: BluetoothItemViewHolder, position: Int) {
        val viewModel = bluetoothDevices[position]
        holder.binding.viewModel = viewModel
    }

    override fun onViewAttachedToWindow(holder: BluetoothItemViewHolder) {
        super.onViewAttachedToWindow(holder)

        holder.binding.viewModel?.didResume()
    }

    override fun onViewDetachedFromWindow(holder: BluetoothItemViewHolder) {
        super.onViewDetachedFromWindow(holder)

        holder.binding.viewModel?.didPause()
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.bluetooth

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.BindingAdapter
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import com.splendo.kaluga.example.databinding.BluetoothCharacteristicItemBinding
import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothCharacteristicViewModel

object CharacteristicsBinding {

    @BindingAdapter("characteristics")
    @JvmStatic
    fun bindCharacteristics(view: RecyclerView, characteristics: List<BluetoothCharacteristicViewModel>?) {
        val characteristicAdapter = view.adapter as? BluetoothCharacteristicAdapter
            ?: return
        characteristicAdapter.characteristics = characteristics ?: emptyList()
    }
}

class BluetoothCharacteristicAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter<BluetoothCharacteristicAdapter.BluetoothCharacteristicItemViewHolder>() {

    class BluetoothCharacteristicItemViewHolder(val characteristicItem: BluetoothCharacteristicItemBinding) : RecyclerView.ViewHolder(characteristicItem.root)

    internal var characteristics = emptyList<BluetoothCharacteristicViewModel>()
        set(value) {
            field = value
            notifyDataSetChanged()
        }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): BluetoothCharacteristicItemViewHolder {
        val binding = BluetoothCharacteristicItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        binding.lifecycleOwner = lifecycleOwner
        binding.descriptors.adapter = BluetoothDescriptorAdapter(lifecycleOwner)
        return BluetoothCharacteristicItemViewHolder(binding)
    }

    override fun getItemCount(): Int {
        return characteristics.size
    }

    override fun onBindViewHolder(holder: BluetoothCharacteristicItemViewHolder, position: Int) {
        holder.characteristicItem.viewModel = characteristics[position]
    }

    override fun onViewAttachedToWindow(holder: BluetoothCharacteristicItemViewHolder) {
        super.onViewAttachedToWindow(holder)

        holder.characteristicItem.viewModel?.didResume()
    }

    override fun onViewDetachedFromWindow(holder: BluetoothCharacteristicItemViewHolder) {
        super.onViewDetachedFromWindow(holder)

        holder.characteristicItem.viewModel?.didPause()
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.bluetooth

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.BindingAdapter
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import com.splendo.kaluga.example.databinding.BluetoothDescriptorItemBinding
import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDescriptorViewModel

object DescriptorsBinding {

    @BindingAdapter("descriptors")
    @JvmStatic
    fun bindDescriptors(view: RecyclerView, descriptors: List<BluetoothDescriptorViewModel>?) {
        val descriptorAdapter = view.adapter as? BluetoothDescriptorAdapter
            ?: return
        descriptorAdapter.descriptors = descriptors ?: emptyList()
    }
}

class BluetoothDescriptorAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter<BluetoothDescriptorAdapter.BluetoothDescriptorItemViewHolder>() {

    class BluetoothDescriptorItemViewHolder(val descriptorItem: BluetoothDescriptorItemBinding) : RecyclerView.ViewHolder(descriptorItem.root)

    var descriptors = emptyList<BluetoothDescriptorViewModel>()
        set(value) {
            field = value
            notifyDataSetChanged()
        }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): BluetoothDescriptorItemViewHolder {
        val binding = BluetoothDescriptorItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        binding.lifecycleOwner = lifecycleOwner
        return BluetoothDescriptorItemViewHolder(binding)
    }

    override fun getItemCount(): Int {
        return descriptors.size
    }

    override fun onBindViewHolder(holder: BluetoothDescriptorItemViewHolder, position: Int) {
        holder.descriptorItem.viewModel = descriptors[position]
    }

    override fun onViewAttachedToWindow(holder: BluetoothDescriptorItemViewHolder) {
        super.onViewAttachedToWindow(holder)

        holder.descriptorItem.viewModel?.didResume()
    }

    override fun onViewDetachedFromWindow(holder: BluetoothDescriptorItemViewHolder) {
        super.onViewDetachedFromWindow(holder)

        holder.descriptorItem.viewModel?.didPause()
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.bluetooth

import android.os.Bundle
import com.splendo.kaluga.architecture.navigation.toNavigationBundle
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.databinding.ActivityBluetoothMoreBinding
import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDeviceDetailViewModel
import com.splendo.kaluga.example.shared.viewmodel.bluetooth.DeviceDetailsSpec
import com.splendo.kaluga.example.shared.viewmodel.bluetooth.DeviceDetailsSpecRow
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf

class BluetoothMoreActivity : KalugaViewModelActivity<BluetoothDeviceDetailViewModel>() {

    override val viewModel: BluetoothDeviceDetailViewModel by viewModel {
        val deviceDetailsSpec = DeviceDetailsSpec()
        intent.extras?.toNavigationBundle(deviceDetailsSpec)?.let { bundle ->
            parametersOf(bundle.get(DeviceDetailsSpecRow.UUIDRow))
        } ?: parametersOf("")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityBluetoothMoreBinding.inflate(layoutInflater, null, false)
        binding.viewModel = viewModel
        binding.lifecycleOwner = this
        setContentView(binding.root)

        binding.serviceList.adapter = BluetoothServiceAdapter(this)
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.bluetooth

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.BindingAdapter
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import com.splendo.kaluga.example.databinding.BluetoothServiceItemBinding
import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothServiceViewModel

object ServicesBinding {

    @BindingAdapter("services")
    @JvmStatic
    fun bindServices(view: RecyclerView, services: List<BluetoothServiceViewModel>?) {
        val serviceAdapter = view.adapter as? BluetoothServiceAdapter
            ?: return
        serviceAdapter.services = services ?: emptyList()
    }
}

class BluetoothServiceAdapter(private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter<BluetoothServiceAdapter.BluetoothServiceItemViewHolder>() {

    class BluetoothServiceItemViewHolder(val serviceItem: BluetoothServiceItemBinding) : RecyclerView.ViewHolder(serviceItem.root)

    internal var services: List<BluetoothServiceViewModel> = emptyList()
        set(value) {
            field = value
            notifyDataSetChanged()
        }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): BluetoothServiceItemViewHolder {
        val binding = BluetoothServiceItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        binding.lifecycleOwner = lifecycleOwner
        binding.characteristicsList.adapter = BluetoothCharacteristicAdapter(lifecycleOwner)
        return BluetoothServiceItemViewHolder(binding)
    }

    override fun getItemCount(): Int {
        return services.size
    }

    override fun onBindViewHolder(holder: BluetoothServiceItemViewHolder, position: Int) {
        holder.serviceItem.viewModel = services[position]
    }

    override fun onViewAttachedToWindow(holder: BluetoothServiceItemViewHolder) {
        super.onViewAttachedToWindow(holder)

        holder.serviceItem.viewModel?.didResume()
    }

    override fun onViewDetachedFromWindow(holder: BluetoothServiceItemViewHolder) {
        super.onViewDetachedFromWindow(holder)

        holder.serviceItem.viewModel?.didPause()
    }
}

package com.splendo.kaluga.example.datetimepicker

import android.os.Bundle
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.databinding.ActivityDateTimePickerBinding
import com.splendo.kaluga.example.shared.viewmodel.datetimepicker.DateTimePickerViewModel
import org.koin.android.ext.android.inject

class DateTimePickerActivity : KalugaViewModelActivity<DateTimePickerViewModel>() {

    override val viewModel: DateTimePickerViewModel by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityDateTimePickerBinding.inflate(layoutInflater, null, false)
        setContentView(binding.root)
        binding.viewModel = viewModel
        binding.lifecycleOwner = this
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.example.di

/* ktlint-disable no-wildcard-imports */
import com.splendo.kaluga.alerts.AlertPresenter
import com.splendo.kaluga.architecture.navigation.ActivityNavigator
import com.splendo.kaluga.architecture.navigation.NavigationSpec
import com.splendo.kaluga.bluetooth.*
import com.splendo.kaluga.bluetooth.beacons.Beacons
import com.splendo.kaluga.datetimepicker.DateTimePickerPresenter
import com.splendo.kaluga.example.FeaturesListFragment
import com.splendo.kaluga.example.InfoDialog
import com.splendo.kaluga.example.InfoFragment
import com.splendo.kaluga.example.R
import com.splendo.kaluga.example.alerts.AlertsActivity
import com.splendo.kaluga.example.architecture.ArchitectureDetailsActivity
import com.splendo.kaluga.example.architecture.ArchitectureInputActivity
import com.splendo.kaluga.example.beacons.BeaconsActivity
import com.splendo.kaluga.example.bluetooth.BluetoothActivity
import com.splendo.kaluga.example.bluetooth.BluetoothMoreActivity
import com.splendo.kaluga.example.datetimepicker.DateTimePickerActivity
import com.splendo.kaluga.example.keyboard.KeyboardManagerActivity
import com.splendo.kaluga.example.link.LinksActivity
import com.splendo.kaluga.example.loading.LoadingActivity
import com.splendo.kaluga.example.location.LocationActivity
import com.splendo.kaluga.example.permissions.PermissionsDemoActivity
import com.splendo.kaluga.example.permissions.PermissionsDemoListActivity
import com.splendo.kaluga.example.platformspecific.PlatformSpecificActivity
import com.splendo.kaluga.example.resources.ButtonActivity
import com.splendo.kaluga.example.resources.ColorActivity
import com.splendo.kaluga.example.resources.LabelActivity
import com.splendo.kaluga.example.resources.ResourcesActivity
import com.splendo.kaluga.example.shared.AlertViewModel
import com.splendo.kaluga.example.shared.HudViewModel
import com.splendo.kaluga.example.shared.viewmodel.ExampleTabNavigation.FeatureList
import com.splendo.kaluga.example.shared.viewmodel.ExampleTabNavigation.Info
import com.splendo.kaluga.example.shared.viewmodel.ExampleViewModel
import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureDetailsViewModel
import com.splendo.kaluga.example.shared.viewmodel.architecture.ArchitectureInputViewModel
import com.splendo.kaluga.example.shared.viewmodel.architecture.InputDetails
import com.splendo.kaluga.example.shared.viewmodel.beacons.BeaconsListViewModel
import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothDeviceDetailViewModel
import com.splendo.kaluga.example.shared.viewmodel.bluetooth.BluetoothListViewModel
import com.splendo.kaluga.example.shared.viewmodel.datetimepicker.DateTimePickerViewModel
import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListNavigationAction
import com.splendo.kaluga.example.shared.viewmodel.featureList.FeatureListViewModel
import com.splendo.kaluga.example.shared.viewmodel.info.DialogSpecRow
import com.splendo.kaluga.example.shared.viewmodel.info.InfoNavigation
import com.splendo.kaluga.example.shared.viewmodel.info.InfoViewModel
import com.splendo.kaluga.example.shared.viewmodel.info.LinkSpecRow
import com.splendo.kaluga.example.shared.viewmodel.info.MailSpecRow
import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel
import com.splendo.kaluga.example.shared.viewmodel.link.BrowserNavigationActions
import com.splendo.kaluga.example.shared.viewmodel.link.BrowserSpecRow
import com.splendo.kaluga.example.shared.viewmodel.link.LinksViewModel
import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel
import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionViewModel
import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionsListViewModel
import com.splendo.kaluga.example.shared.viewmodel.resources.ButtonViewModel
import com.splendo.kaluga.example.shared.viewmodel.resources.ColorViewModel
import com.splendo.kaluga.example.shared.viewmodel.resources.LabelViewModel
import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListNavigationAction
import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListViewModel
import com.splendo.kaluga.example.shared.viewmodel.system.SystemNavigationActions
import com.splendo.kaluga.example.shared.viewmodel.system.SystemViewModel
import com.splendo.kaluga.example.shared.viewmodel.system.network.NetworkViewModel
import com.splendo.kaluga.example.system.SystemActivity
import com.splendo.kaluga.example.system.fragments.NetworkFragment
import com.splendo.kaluga.hud.HUD
import com.splendo.kaluga.keyboard.FocusHandler
import com.splendo.kaluga.keyboard.KeyboardManager
import com.splendo.kaluga.links.LinksBuilder
import com.splendo.kaluga.location.LocationStateRepoBuilder
import com.splendo.kaluga.permissions.Permission
import com.splendo.kaluga.permissions.Permissions
import com.splendo.kaluga.permissions.PermissionsBuilder
import com.splendo.kaluga.permissions.bluetooth.registerBluetoothPermission
import com.splendo.kaluga.permissions.calendar.registerCalendarPermission
import com.splendo.kaluga.permissions.camera.registerCameraPermission
import com.splendo.kaluga.permissions.contacts.registerContactsPermission
import com.splendo.kaluga.permissions.location.LocationPermission
import com.splendo.kaluga.permissions.location.registerLocationPermission
import com.splendo.kaluga.permissions.microphone.registerMicrophonePermission
import com.splendo.kaluga.permissions.notifications.registerNotificationsPermission
import com.splendo.kaluga.permissions.storage.registerStoragePermission
import com.splendo.kaluga.resources.StyledStringBuilder
import com.splendo.kaluga.review.ReviewManager
import com.splendo.kaluga.system.network.state.NetworkStateRepoBuilder
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
import java.net.URL

val utilitiesModule = module {
    single {
        Permissions(
            PermissionsBuilder().apply {
                registerBluetoothPermission()
                registerCameraPermission()
                registerStoragePermission()
                registerLocationPermission()
                registerNotificationsPermission()
                registerContactsPermission()
                registerMicrophonePermission()
                registerCalendarPermission()
            }
        )
    }
    single { LocationStateRepoBuilder() }
    single { BluetoothBuilder().create() }
    single { Beacons(get<Bluetooth>(), timeoutMs = 60_000) }
}

val viewModelModule = module {
    viewModel {
        ExampleViewModel(
            ActivityNavigator { action ->
                val navigationSpec: NavigationSpec = when (action) {
                    FeatureList -> NavigationSpec.Fragment(
                        R.id.example_fragment,
                        createFragment = { FeaturesListFragment() }
                    )
                    Info -> NavigationSpec.Fragment(
                        R.id.example_fragment,
                        createFragment = { InfoFragment() }
                    )
                }
                navigationSpec
            }
        )
    }

    viewModel {
        FeatureListViewModel(
            ActivityNavigator { action ->
                when (action) {
                    FeatureListNavigationAction.Location -> NavigationSpec.Activity(LocationActivity::class.java)
                    FeatureListNavigationAction.Permissions -> NavigationSpec.Activity(PermissionsDemoListActivity::class.java)
                    FeatureListNavigationAction.Alerts -> NavigationSpec.Activity(AlertsActivity::class.java)
                    FeatureListNavigationAction.DateTimePicker -> NavigationSpec.Activity(DateTimePickerActivity::class.java)
                    FeatureListNavigationAction.LoadingIndicator -> NavigationSpec.Activity(LoadingActivity::class.java)
                    FeatureListNavigationAction.Architecture -> NavigationSpec.Activity(ArchitectureInputActivity::class.java)
                    FeatureListNavigationAction.Keyboard -> NavigationSpec.Activity(KeyboardManagerActivity::class.java)
                    FeatureListNavigationAction.Links -> NavigationSpec.Activity(LinksActivity::class.java)
                    FeatureListNavigationAction.System -> NavigationSpec.Activity(SystemActivity::class.java)
                    FeatureListNavigationAction.Bluetooth -> NavigationSpec.Activity(BluetoothActivity::class.java)
                    FeatureListNavigationAction.Beacons -> NavigationSpec.Activity(BeaconsActivity::class.java)
                    FeatureListNavigationAction.Resources -> NavigationSpec.Activity(ResourcesActivity::class.java)
                    FeatureListNavigationAction.PlatformSpecific -> NavigationSpec.Activity(PlatformSpecificActivity::class.java)
                }
            }
        )
    }

    viewModel {
        InfoViewModel(
            ReviewManager.Builder(),
            ActivityNavigator { action ->
                when (action) {
                    is InfoNavigation.Dialog -> {
                        val title = action.bundle?.get(DialogSpecRow.TitleRow) ?: ""
                        val message = action.bundle?.get(DialogSpecRow.MessageRow) ?: ""
                        NavigationSpec.Dialog(
                            createDialog = {
                                InfoDialog(title, message)
                            }
                        )
                    }
                    is InfoNavigation.Link -> NavigationSpec.Browser(
                        URL(action.bundle!!.get(LinkSpecRow.LinkRow)),
                        NavigationSpec.Browser.Type.Normal
                    )
                    is InfoNavigation.Mail -> NavigationSpec.Email(
                        NavigationSpec.Email.EmailSettings(
                            to = action.bundle?.get(MailSpecRow.ToRow) ?: emptyList(),
                            subject = action.bundle?.get(MailSpecRow.SubjectRow)
                        )
                    )
                }
            }
        )
    }

    viewModel {
        PermissionsListViewModel(
            ActivityNavigator {
                NavigationSpec.Activity(PermissionsDemoActivity::class.java)
            }
        )
    }

    viewModel { (permission: Permission) -> PermissionViewModel(get(), permission) }

    viewModel { (permission: LocationPermission) -> LocationViewModel(permission, get()) }

    viewModel {
        ArchitectureInputViewModel(
            ActivityNavigator {
                NavigationSpec.Activity(ArchitectureDetailsActivity::class.java, requestCode = ArchitectureInputActivity.requestCode)
            }
        )
    }

    viewModel { (initialDetail: InputDetails) ->
        ArchitectureDetailsViewModel(
            initialDetail,
            ActivityNavigator {
                NavigationSpec.Close(ArchitectureDetailsActivity.resultCode)
            }
        )
    }

    viewModel {
        AlertViewModel(AlertPresenter.Builder())
    }

    viewModel {
        DateTimePickerViewModel(DateTimePickerPresenter.Builder())
    }

    viewModel {
        HudViewModel(HUD.Builder())
    }

    viewModel { (keyboardBuilder: KeyboardManager.Builder, focusHandler: FocusHandler) ->
        KeyboardViewModel(keyboardBuilder, focusHandler)
    }

    viewModel {
        LinksViewModel(
            LinksBuilder(),
            AlertPresenter.Builder(),
            ActivityNavigator {
                when (it) {
                    is BrowserNavigationActions.OpenWebView -> NavigationSpec.Browser(
                        URL(it.bundle!!.get(BrowserSpecRow.UrlSpecRow)),
                        NavigationSpec.Browser.Type.Normal
                    )
                }
            }
        )
    }

    viewModel {
        SystemViewModel(
            ActivityNavigator {
                when (it) {
                    SystemNavigationActions.Network -> NavigationSpec.Fragment(
                        R.id.system_features_fragment,
                        createFragment = { NetworkFragment() }
                    )
                }
            }
        )
    }

    viewModel {
        NetworkViewModel(NetworkStateRepoBuilder(get()))
    }

    viewModel {
        BluetoothListViewModel(
            get(),
            ActivityNavigator {
                NavigationSpec.Activity(BluetoothMoreActivity::class.java)
            }
        )
    }

    viewModel { (identifier: com.splendo.kaluga.bluetooth.device.Identifier) ->
        BluetoothDeviceDetailViewModel(get(), identifier)
    }

    viewModel {
        BeaconsListViewModel(get())
    }

    viewModel {
        ResourcesListViewModel(
            ActivityNavigator {
                when (it) {
                    is ResourcesListNavigationAction.Label -> NavigationSpec.Activity(LabelActivity::class.java)
                    is ResourcesListNavigationAction.Color -> NavigationSpec.Activity(ColorActivity::class.java)
                    is ResourcesListNavigationAction.Button -> NavigationSpec.Activity(ButtonActivity::class.java)
                }
            }
        )
    }

    viewModel {
        ColorViewModel(AlertPresenter.Builder())
    }

    viewModel {
        LabelViewModel(StyledStringBuilder.Provider())
    }

    viewModel {
        ButtonViewModel(StyledStringBuilder.Provider(), AlertPresenter.Builder())
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.example.keyboard

import android.os.Bundle
import androidx.appcompat.widget.AppCompatButton
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.R
import com.splendo.kaluga.example.shared.viewmodel.keyboard.KeyboardViewModel
import com.splendo.kaluga.keyboard.AndroidFocusHandler
import com.splendo.kaluga.keyboard.keyboardManagerBuilder
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf

class KeyboardManagerActivity : KalugaViewModelActivity<KeyboardViewModel>(R.layout.activity_keyboard_manager) {

    override val viewModel: KeyboardViewModel by viewModel {
        parametersOf(
            keyboardManagerBuilder(),
            AndroidFocusHandler(R.id.edit_field)
        )
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        findViewById<AppCompatButton>(R.id.btn_show_keyboard).setOnClickListener { viewModel.onShowPressed() }
        findViewById<AppCompatButton>(R.id.btn_hide_keyboard).setOnClickListener { viewModel.onHidePressed() }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.link

import android.content.Intent
import android.os.Bundle
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.R
import com.splendo.kaluga.example.databinding.ActivityLinkBinding
import com.splendo.kaluga.example.shared.viewmodel.link.LinksViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel
import java.net.URL

class LinksActivity : KalugaViewModelActivity<LinksViewModel>(R.layout.activity_link) {

    override val viewModel: LinksViewModel by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setupBindings()

        handleAppLinks(intent)
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        intent?.let {
            handleAppLinks(it)
        }
    }

    private fun handleAppLinks(intent: Intent) {
        intent.let { i ->
            i.data?.let { uri ->
                val url = URL(uri.scheme, uri.host, uri.path)
                viewModel.handleIncomingLink("$url?${uri.encodedQuery}")
            }
        }
    }

    private fun setupBindings() {
        val binding = ActivityLinkBinding.inflate(layoutInflater, null, false)
        binding.viewModel = viewModel
        binding.lifecycleOwner = this
        setContentView(binding.root)
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.example.loading

import android.annotation.SuppressLint
import android.os.Bundle
import androidx.appcompat.widget.AppCompatButton
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.R
import com.splendo.kaluga.example.shared.HudViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel

@SuppressLint("SetTextI18n")
class LoadingActivity : KalugaViewModelActivity<HudViewModel>(R.layout.activity_loading) {

    override val viewModel: HudViewModel by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        findViewById<AppCompatButton>(R.id.btn_show_loading_indicator_system).setOnClickListener {
            viewModel.onShowSystemPressed()
        }

        findViewById<AppCompatButton>(R.id.btn_show_loading_indicator_custom).setOnClickListener {
            viewModel.onShowCustomPressed()
        }
    }
}

/*

Copyright 2019 Splendo Consulting B.V. The Netherlands

   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.splendo.kaluga.example.location

import android.content.Intent
import android.os.Bundle
import androidx.appcompat.widget.AppCompatButton
import androidx.appcompat.widget.AppCompatTextView
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.R
import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel
import com.splendo.kaluga.permissions.location.LocationPermission
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf

class LocationActivity : KalugaViewModelActivity<LocationViewModel>(R.layout.activity_location) {

    companion object {
        private val permission = LocationPermission(background = false, precise = true)
    }

    override val viewModel: LocationViewModel by viewModel {
        parametersOf(permission)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        findViewById<AppCompatButton>(R.id.enable_background).setOnClickListener {
            startService(Intent(applicationContext, LocationBackgroundService::class.java))
        }

        findViewById<AppCompatButton>(R.id.disable_background).setOnClickListener {
            stopService(Intent(applicationContext, LocationBackgroundService::class.java))
        }

        viewModel.location.observeInitialized {
            val info = findViewById<AppCompatTextView>(R.id.info)
            info.text = it
            info.animate().withEndAction {
                info.animate().setDuration(10000).alpha(0.12f).start()
            }.alpha(1f).setDuration(100).start()
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.location

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.splendo.kaluga.example.R
import com.splendo.kaluga.example.shared.viewmodel.location.LocationViewModel
import com.splendo.kaluga.permissions.location.LocationPermission
import org.koin.core.component.KoinComponent
import org.koin.core.component.get

class LocationBackgroundService : androidx.lifecycle.LifecycleService(), KoinComponent {

    companion object {
        const val notificationId = 1
        const val channelId = "location_channel"
        const val channelName = "Kaluga Location"

        private val permission = LocationPermission(background = true, precise = true)
    }

    private val notificationService by lazy { applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }

    private val viewModel = LocationViewModel(permission, get())

    override fun onCreate() {
        super.onCreate()

        viewModel.location.observeInitialized { message ->
            NotificationManagerCompat.from(applicationContext).notify(notificationId, getNotification(message))
        }

        startForeground(notificationId, getNotification(""))
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)
        viewModel.didResume()
        return START_NOT_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        viewModel.didPause()
        viewModel.onCleared()
        stopForeground(true)
        NotificationManagerCompat.from(applicationContext).cancel(notificationId)
    }

    private fun createChannelIfNeeded() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && notificationService.getNotificationChannel(
                channelId
            ) == null
        ) {
            val importance = NotificationManager.IMPORTANCE_DEFAULT
            val channel = NotificationChannel(channelId, channelName, importance)
            channel.setSound(null, null)
            channel.enableVibration(false)
            channel.setShowBadge(false)
            notificationService.createNotificationChannel(channel)
        }
    }

    private fun getNotification(message: String): Notification {
        createChannelIfNeeded()
        val builder = NotificationCompat.Builder(applicationContext, channelId)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle(applicationContext.getString(R.string.location_background))
            .setContentText(message)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setSound(null)
            .setVibrate(null)
            .setBadgeIconType(NotificationCompat.BADGE_ICON_NONE)
        val notification = builder.build()

        notification.flags = Notification.FLAG_NO_CLEAR or Notification.FLAG_ONGOING_EVENT
        return notification
    }
}

/*
 Copyright (c) 2020. Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.permissions

import android.os.Bundle
import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.widget.AppCompatButton
import com.splendo.kaluga.architecture.navigation.toNavigationBundle
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.R
import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionNavigationBundleSpec
import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionNavigationBundleSpecRow
import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionView
import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionViewModel
import com.splendo.kaluga.permissions.bluetooth.BluetoothPermission
import com.splendo.kaluga.permissions.calendar.CalendarPermission
import com.splendo.kaluga.permissions.camera.CameraPermission
import com.splendo.kaluga.permissions.contacts.ContactsPermission
import com.splendo.kaluga.permissions.location.LocationPermission
import com.splendo.kaluga.permissions.microphone.MicrophonePermission
import com.splendo.kaluga.permissions.notifications.NotificationsPermission
import com.splendo.kaluga.permissions.storage.StoragePermission
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf

class PermissionsDemoActivity : KalugaViewModelActivity<PermissionViewModel>(R.layout.activity_permissions_demo) {

    override val viewModel: PermissionViewModel by viewModel {
        val permissionNavSpec = PermissionNavigationBundleSpec()
        intent.extras?.toNavigationBundle(permissionNavSpec)?.let { bundle ->
            val permission = when (bundle.get(PermissionNavigationBundleSpecRow)) {
                PermissionView.Bluetooth -> BluetoothPermission
                PermissionView.Calendar -> CalendarPermission(allowWrite = true)
                PermissionView.Camera -> CameraPermission
                PermissionView.Contacts -> ContactsPermission(allowWrite = true)
                PermissionView.Location -> LocationPermission(background = true, precise = true)
                PermissionView.Microphone -> MicrophonePermission
                PermissionView.Notifications -> NotificationsPermission()
                PermissionView.Storage -> StoragePermission(allowWrite = true)
            }
            parametersOf(permission)
        } ?: parametersOf()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val permissionNavSpec = PermissionNavigationBundleSpec()
        intent.extras?.toNavigationBundle(permissionNavSpec)?.let { bundle ->
            supportActionBar?.title = bundle.get(PermissionNavigationBundleSpecRow).title
        }

        viewModel.permissionStateMessage.observe {
            findViewById<TextView>(R.id.permissions_message).text = it
        }

        viewModel.showPermissionButton.observe {
            findViewById<AppCompatButton>(R.id.btn_permissions_bluetooth_request_permissions).visibility = if (it == true) View.VISIBLE else View.GONE
        }

        findViewById<AppCompatButton>(R.id.btn_permissions_bluetooth_request_permissions).setOnClickListener { viewModel.requestPermission() }

        viewModel.requestMessage.observeInitialized { message ->
            if (message != null) Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.permissions

import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.widget.AppCompatButton
import androidx.recyclerview.widget.RecyclerView
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.R
import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionView
import com.splendo.kaluga.example.shared.viewmodel.permissions.PermissionsListViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel

class PermissionsDemoListActivity : KalugaViewModelActivity<PermissionsListViewModel>(R.layout.activity_permissions_list) {

    override val viewModel: PermissionsListViewModel by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val adapter = PermissionsAdapter(viewModel).apply {
            findViewById<RecyclerView>(R.id.permissions_list).adapter = this
        }
        viewModel.permissions.observeInitialized { adapter.permissions = it }
    }
}

class PermissionsAdapter(private val viewModel: PermissionsListViewModel) : RecyclerView.Adapter<PermissionsAdapter.PermissionsViewHolder>() {

    class PermissionsViewHolder(val button: AppCompatButton) : RecyclerView.ViewHolder(button)

    var permissions: List<PermissionView> = emptyList()
        set(newValue) {
            field = newValue
            notifyDataSetChanged()
        }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PermissionsViewHolder {
        val button = LayoutInflater.from(parent.context).inflate(R.layout.view_list_button, parent, false) as AppCompatButton
        return PermissionsViewHolder(button)
    }

    override fun getItemCount(): Int = permissions.size

    override fun onBindViewHolder(holder: PermissionsViewHolder, position: Int) {
        permissions.getOrNull(position)?.let { permission ->
            holder.button.text = permission.title
            holder.button.setOnClickListener { viewModel.onPermissionPressed(permission) }
        } ?: run {
            holder.button.text = null
            holder.button.setOnClickListener(null)
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.platformspecific

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.google.android.material.composethemeadapter.MdcTheme
import com.splendo.kaluga.architecture.compose.state
import com.splendo.kaluga.architecture.compose.viewModel.KalugaViewModelComposeActivity
import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable
import com.splendo.kaluga.architecture.navigation.ActivityNavigator
import com.splendo.kaluga.architecture.navigation.NavigationSpec
import com.splendo.kaluga.example.platformspecific.compose.bottomSheet.BottomSheetActivity
import com.splendo.kaluga.example.platformspecific.compose.contacts.ContactsActivity
import com.splendo.kaluga.example.platformspecific.compose.contacts.ui.Padding
import com.splendo.kaluga.example.shared.viewmodel.featureList.PlatformFeatureListNavigationAction
import com.splendo.kaluga.example.shared.viewmodel.featureList.PlatformSpecificFeaturesViewModel

class PlatformSpecificActivity : KalugaViewModelComposeActivity<PlatformSpecificFeaturesViewModel>() {
    override val viewModel = PlatformSpecificFeaturesViewModel(ActivityNavigator(::navigationMapper))

    @Composable
    override fun Layout(viewModel: PlatformSpecificFeaturesViewModel) {
        PlatformSpecificFeaturesLayout(viewModel)
    }
}

private fun navigationMapper(action: PlatformFeatureListNavigationAction): NavigationSpec =
    when (action) {
        is PlatformFeatureListNavigationAction.ComposeNavigation -> NavigationSpec.Activity(ContactsActivity::class.java)
        is PlatformFeatureListNavigationAction.ComposeBottomSheet -> NavigationSpec.Activity(BottomSheetActivity::class.java)
    }

@Composable
private fun PlatformSpecificFeaturesLayout(viewModel: PlatformSpecificFeaturesViewModel) {
    MdcTheme {
        ViewModelComposable(viewModel) {
            val features by feature.state()
            LazyColumn {
                items(features) { item ->
                    Button(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(Padding.default),
                        onClick = { onFeaturePressed(item) },
                    ) {
                        Text(
                            text = item.title.uppercase(),
                            style = MaterialTheme.typography.button,
                        )
                    }
                }
            }
        }
    }
}

@Composable
@Preview
private fun PlatformSpecificFeaturesLayoutPreview() {
    PlatformSpecificFeaturesLayout(
        PlatformSpecificFeaturesViewModel(ActivityNavigator(::navigationMapper))
    )
}

/*
 * Copyright 2021 Splendo Consulting B.V. The Netherlands
 *
 *     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.splendo.kaluga.example.resources

import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.databinding.ActivityResourcesBinding
import com.splendo.kaluga.example.databinding.ViewListButtonBinding
import com.splendo.kaluga.example.shared.viewmodel.resources.ButtonViewModel
import com.splendo.kaluga.example.view.VerticalSpaceItemDecoration
import com.splendo.kaluga.resources.dpToPixel
import com.splendo.kaluga.resources.view.KalugaButton
import com.splendo.kaluga.resources.view.bindButton
import org.koin.androidx.viewmodel.ext.android.viewModel

class ButtonActivity : KalugaViewModelActivity<ButtonViewModel>() {

    override val viewModel: ButtonViewModel by viewModel()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityResourcesBinding.inflate(layoutInflater, null, false)
        setContentView(binding.root)

        val adapter = ButtonAdapter().apply {

            binding.resourcesList.adapter = this
        }
        viewModel.buttons.observeInitialized { adapter.buttons = it }
        binding.resourcesList.addItemDecoration(VerticalSpaceItemDecoration(10.0f.dpToPixel(this).toInt()))
    }
}

class ButtonAdapter : RecyclerView.Adapter<ButtonAdapter.ButtonViewHolder>() {

    class ButtonViewHolder(val binding: ViewListButtonBinding) : RecyclerView.ViewHolder(binding.root) {
        val button = binding.button
    }

    var buttons: List<KalugaButton> = emptyList()
        set(newValue) {
            field = newValue
            notifyDataSetChanged()
        }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ButtonViewHolder {
        val binding = ViewListButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ButtonViewHolder(binding)
    }

    override fun getItemCount(): Int = buttons.size

    override fun onBindViewHolder(holder: ButtonViewHolder, position: Int) {
        buttons.getOrNull(position)?.let { button ->
            holder.button.bindButton(button)
        } ?: run {
            holder.button.text = null
            holder.button.background = null
            holder.button.setOnClickListener(null)
        }
    }
}

/*
 * Copyright 2021 Splendo Consulting B.V. The Netherlands
 *
 *     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.splendo.kaluga.example.resources

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.Button
import android.widget.TextView
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.databinding.ActivityResourcesColorBinding
import com.splendo.kaluga.example.databinding.ViewResourceListBackgroundBinding
import com.splendo.kaluga.example.shared.viewmodel.resources.ColorViewModel
import com.splendo.kaluga.resources.DefaultColors
import com.splendo.kaluga.resources.stylable.BackgroundStyle
import com.splendo.kaluga.resources.view.KalugaButton
import com.splendo.kaluga.resources.view.KalugaLabel
import com.splendo.kaluga.resources.view.applyBackgroundStyle
import com.splendo.kaluga.resources.view.bindButton
import com.splendo.kaluga.resources.view.bindLabel
import org.koin.androidx.viewmodel.ext.android.viewModel

class ColorActivity : KalugaViewModelActivity<ColorViewModel>() {

    override val viewModel: ColorViewModel by viewModel()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityResourcesColorBinding.inflate(layoutInflater, null, false)
        binding.viewModel = viewModel
        binding.lifecycleOwner = this
        setContentView(binding.root)

        binding.backdropEdit.setOnEditorActionListener { v, actionId, _ ->
            if (actionId == EditorInfo.IME_ACTION_DONE) {
                viewModel.submitBackdropText(v.text.toString())
                true
            } else {
                false
            }
        }

        binding.sourceEdit.setOnEditorActionListener { v, actionId, _ ->
            if (actionId == EditorInfo.IME_ACTION_DONE) {
                viewModel.submitSourceText(v.text.toString())
                true
            } else {
                false
            }
        }

        binding.backdropLighten.adapter = BackgroundAdapter()
        binding.backdropDarken.adapter = BackgroundAdapter()
        binding.blendedLighten.adapter = BackgroundAdapter()
        binding.blendedDarken.adapter = BackgroundAdapter()
        binding.sourceLighten.adapter = BackgroundAdapter()
        binding.sourceDarken.adapter = BackgroundAdapter()
    }
}

class BackgroundAdapter : RecyclerView.Adapter<BackgroundAdapter.BackgroundViewHolder>() {

    class BackgroundViewHolder(val binding: ViewResourceListBackgroundBinding) : RecyclerView.ViewHolder(binding.root) {
        val view = binding.root
    }

    var backgrounds: List<BackgroundStyle> = emptyList()
        set(newValue) {
            field = newValue
            notifyDataSetChanged()
        }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackgroundViewHolder {
        val binding = ViewResourceListBackgroundBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return BackgroundViewHolder(binding)
    }

    override fun getItemCount(): Int = backgrounds.size

    override fun onBindViewHolder(holder: BackgroundViewHolder, position: Int) {
        backgrounds.getOrNull(position)?.let { background ->
            holder.view.applyBackgroundStyle(background)
        } ?: run {
            holder.view.setBackgroundColor(DefaultColors.clear)
        }
    }
}

object ResourcesBinding {

    @BindingAdapter("button")
    @JvmStatic
    fun bindButton(view: Button, button: KalugaButton?) {
        button?.let {
            view.bindButton(it)
        }
    }

    @BindingAdapter("label")
    @JvmStatic
    fun bindLabel(view: TextView, label: KalugaLabel?) {
        label?.let {
            view.bindLabel(it)
        }
    }

    @BindingAdapter("backgroundStyle")
    @JvmStatic
    fun bindBackgroundStyle(view: View, backgroundStyle: BackgroundStyle?) {
        backgroundStyle?.let {
            view.applyBackgroundStyle(it)
        }
    }

    @BindingAdapter("backgrounds")
    @JvmStatic
    fun bindBackgrounds(recyclerView: RecyclerView, backgrounds: List<BackgroundStyle>?) {
        val adapter = recyclerView.adapter as? BackgroundAdapter ?: return
        adapter.backgrounds = backgrounds ?: emptyList()
    }
}

/*
 * Copyright 2021 Splendo Consulting B.V. The Netherlands
 *
 *     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.splendo.kaluga.example.resources

import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.databinding.ActivityResourcesBinding
import com.splendo.kaluga.example.databinding.ViewListTextViewBinding
import com.splendo.kaluga.example.shared.viewmodel.resources.LabelViewModel
import com.splendo.kaluga.example.view.VerticalSpaceItemDecoration
import com.splendo.kaluga.resources.dpToPixel
import com.splendo.kaluga.resources.view.KalugaLabel
import com.splendo.kaluga.resources.view.bindLabel
import org.koin.androidx.viewmodel.ext.android.viewModel

class LabelActivity : KalugaViewModelActivity<LabelViewModel>() {

    override val viewModel: LabelViewModel by viewModel()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityResourcesBinding.inflate(layoutInflater, null, false)
        setContentView(binding.root)

        val adapter = LabelAdapter().apply {

            binding.resourcesList.adapter = this
        }
        binding.resourcesList.addItemDecoration(VerticalSpaceItemDecoration(10.0f.dpToPixel(this).toInt()))
        viewModel.labels.observeInitialized { adapter.labels = it }
    }
}

class LabelAdapter : RecyclerView.Adapter<LabelAdapter.LabelViewHolder>() {

    class LabelViewHolder(val binding: ViewListTextViewBinding) : RecyclerView.ViewHolder(binding.root) {
        val label = binding.label
    }

    var labels: List<KalugaLabel> = emptyList()
        set(newValue) {
            field = newValue
            notifyDataSetChanged()
        }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LabelViewHolder {
        val binding = ViewListTextViewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return LabelViewHolder(binding)
    }

    override fun getItemCount(): Int = labels.size

    override fun onBindViewHolder(holder: LabelViewHolder, position: Int) {
        labels.getOrNull(position)?.let { label ->
            holder.label.bindLabel(label)
        } ?: run {
            holder.label.text = null
        }
    }
}

package com.splendo.kaluga.example.resources

import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.databinding.ActivityResourcesBinding
import com.splendo.kaluga.example.databinding.ViewListButtonBinding
import com.splendo.kaluga.example.shared.viewmodel.resources.Resource
import com.splendo.kaluga.example.shared.viewmodel.resources.ResourcesListViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel

class ResourcesActivity : KalugaViewModelActivity<ResourcesListViewModel>() {
    override val viewModel: ResourcesListViewModel by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityResourcesBinding.inflate(layoutInflater, null, false)
        setContentView(binding.root)

        val adapter = ResourcesAdapter(viewModel).apply {

            binding.resourcesList.adapter = this
        }
        viewModel.resources.observeInitialized { adapter.resources = it }
    }
}

class ResourcesAdapter(private val viewModel: ResourcesListViewModel) :
    RecyclerView.Adapter<ResourcesAdapter.ResourceViewHolder>() {

    class ResourceViewHolder(val binding: ViewListButtonBinding) :
        RecyclerView.ViewHolder(binding.root) {
        val button = binding.button
    }

    var resources: List<Resource> = emptyList()
        set(newValue) {
            field = newValue
            notifyDataSetChanged()
        }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ResourceViewHolder {
        val binding = ViewListButtonBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ResourceViewHolder(binding)
    }

    override fun getItemCount(): Int = resources.size

    override fun onBindViewHolder(holder: ResourceViewHolder, position: Int) {
        resources.getOrNull(position)?.let { resource ->
            holder.button.text = resource.title
            holder.button.setOnClickListener { viewModel.onResourceSelected(resource) }
        } ?: run {
            holder.button.text = null
            holder.button.setOnClickListener(null)
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.system

import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Button
import androidx.appcompat.widget.AppCompatButton
import androidx.recyclerview.widget.RecyclerView
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelActivity
import com.splendo.kaluga.example.R
import com.splendo.kaluga.example.shared.viewmodel.system.SystemFeatures
import com.splendo.kaluga.example.shared.viewmodel.system.SystemViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel

class SystemActivity : KalugaViewModelActivity<SystemViewModel>(R.layout.activity_system) {
    override val viewModel: SystemViewModel by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val adapter = SystemFeatureAdapter(viewModel).apply {
            findViewById<RecyclerView>(R.id.system_features_list).adapter = this
        }

        viewModel.modules.observeInitialized {
            adapter.modules = it
        }
    }
}

class SystemFeatureAdapter(
    private val viewModel: SystemViewModel
) : RecyclerView.Adapter<SystemFeatureAdapter.SystemFeatureViewHolder>() {

    inner class SystemFeatureViewHolder(val button: Button) : RecyclerView.ViewHolder(button)

    var modules: List<SystemFeatures> = emptyList()
        set(newValue) {
            field = newValue
            notifyDataSetChanged()
        }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SystemFeatureViewHolder {
        val button = LayoutInflater.from(parent.context).inflate(R.layout.view_list_button, parent, false) as AppCompatButton
        return SystemFeatureViewHolder(button)
    }

    override fun onBindViewHolder(holder: SystemFeatureViewHolder, position: Int) {
        modules.getOrNull(position)?.let { feature ->
            holder.button.text = feature.name
            holder.button.setOnClickListener { viewModel.onButtonTapped(feature) }
        } ?: run {
            holder.button.text = null
            holder.button.setOnClickListener(null)
        }
    }

    override fun getItemCount(): Int = modules.size
}

/*
 * Copyright 2021 Splendo Consulting B.V. The Netherlands
 *
 *     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.splendo.kaluga.example.view

import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class VerticalSpaceItemDecoration(private val verticalSpaceHeight: Int) : RecyclerView.ItemDecoration() {
    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        outRect.bottom = verticalSpaceHeight
    }
}

class HorizontalSpaceItemDecoration(private val horizontalSpaceWidth: Int) : RecyclerView.ItemDecoration() {
    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        outRect.right = horizontalSpaceWidth
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.system.network.state

import android.content.Context
import com.splendo.kaluga.base.ApplicationHolder
import com.splendo.kaluga.system.network.NetworkManagerBuilder

actual class NetworkStateRepoBuilder(
    private val context: Context = ApplicationHolder.applicationContext,
) : NetworkStateRepo.Builder {

    override fun create(): NetworkStateRepo =
        NetworkStateRepo(NetworkManagerBuilder(context))
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.system.network.state

import com.splendo.kaluga.state.State
import com.splendo.kaluga.system.network.Network

sealed class NetworkState(
    open val networkType: Network,
) : State() {

    data class Unknown(
        override val networkType: Network.Unknown
    ) : NetworkState(networkType) {

        val availableWithWifi: suspend () -> Available = {
            Available(Network.Known.Wifi())
        }

        val availableWithCellular: suspend () -> Available = {
            Available(Network.Known.Cellular())
        }

        val unavailable: suspend () -> Unavailable = {
            Unavailable(Network.Known.Absent)
        }
    }
    data class Available(
        override val networkType: Network.Known,
    ) : NetworkState(networkType) {

        val unknownWithLastNetwork: suspend (network: Network.Known, reason: Network.Unknown.Reason) -> Unknown = { network, reason ->
            Unknown(Network.Unknown.WithLastNetwork(network, reason))
        }

        val unknownWithoutLastNetwork: suspend (reason: Network.Unknown.Reason) -> Unknown = {
            Unknown(Network.Unknown.WithoutLastNetwork(it))
        }

        val availableWithWifi: suspend () -> Available = {
            Available(Network.Known.Wifi())
        }

        val availableWithCellular: suspend () -> Available = {
            Available(Network.Known.Cellular())
        }

        val unavailable: suspend () -> Unavailable = {
            Unavailable(Network.Known.Absent)
        }
    }

    data class Unavailable(
        override val networkType: Network.Known,
    ) : NetworkState(networkType) {

        val availableWithWifi: suspend () -> Available = {
            Available(Network.Known.Wifi())
        }

        val availableWithCellular: suspend () -> Available = {
            Available(Network.Known.Cellular())
        }

        val unknownWithLastNetwork: suspend (network: Network.Known, reason: Network.Unknown.Reason) -> Unknown = { network, reason ->
            Unknown(Network.Unknown.WithLastNetwork(network, reason))
        }

        val unknownWithoutLastNetwork: suspend (reason: Network.Unknown.Reason) -> Unknown = {
            Unknown(Network.Unknown.WithoutLastNetwork(it))
        }
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.system.network.state

import co.touchlab.stately.concurrency.AtomicReference
import com.splendo.kaluga.base.runBlocking
import com.splendo.kaluga.state.ColdStateRepo
import com.splendo.kaluga.system.network.BaseNetworkManager
import com.splendo.kaluga.system.network.Network
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest

open class NetworkStateRepo(
    private val networkManagerBuilder: BaseNetworkManager.Builder,
) : ColdStateRepo<NetworkState>() {

    interface Builder {
        fun create(): NetworkStateRepo
    }

    private val _lastKnownNetwork = AtomicReference<Network>(Network.Unknown.WithoutLastNetwork(Network.Unknown.Reason.NOT_CLEAR))
    internal var lastKnownNetwork: Network
        get() = _lastKnownNetwork.get()
        set(value) { _lastKnownNetwork.set(value) }

    private val _networkManager = AtomicReference<BaseNetworkManager?>(null)
    internal var networkManager: BaseNetworkManager?
        get() = _networkManager.get()
        set(value) {
            _networkManager.get()?.let {
                if (value == null) {
                    it.dispose()
                }
            }

            _networkManager.set(value)
        }

    override suspend fun deinitialize(state: NetworkState) {
        lastKnownNetwork = state.networkType
        networkManager = null
    }

    override suspend fun initialValue(): NetworkState {
        networkManager = networkManagerBuilder.create(::onNetworkStateChange)

        return when (val network = lastKnownNetwork) {
            is Network.Unknown.WithoutLastNetwork -> NetworkState.Unknown(
                Network.Unknown.WithoutLastNetwork(network.reason)
            )
            is Network.Unknown.WithLastNetwork -> NetworkState.Unknown(
                Network.Unknown.WithLastNetwork(network.lastKnownNetwork, network.reason)
            )
            is Network.Known.Cellular -> NetworkState.Unknown(
                Network.Unknown.WithoutLastNetwork(Network.Unknown.Reason.NOT_CLEAR)
            )
            is Network.Known.Wifi -> NetworkState.Unknown(
                Network.Unknown.WithoutLastNetwork(Network.Unknown.Reason.NOT_CLEAR)
            )
            Network.Known.Absent -> NetworkState.Unknown(
                Network.Unknown.WithoutLastNetwork(Network.Unknown.Reason.NOT_CLEAR)
            )
        }
    }

    protected fun onNetworkStateChange(network: Network) {
        runBlocking {
            takeAndChangeState { state: NetworkState ->
                when (state) {
                    is NetworkState.Available -> {
                        when (network) {
                            is Network.Unknown.WithoutLastNetwork -> {
                                { state.unknownWithoutLastNetwork(network.reason) }
                            }
                            is Network.Unknown.WithLastNetwork -> {
                                { state.unknownWithLastNetwork(network.lastKnownNetwork, network.reason) }
                            }
                            is Network.Known.Cellular -> state.availableWithCellular
                            is Network.Known.Wifi -> state.availableWithWifi
                            Network.Known.Absent -> state.unavailable
                        }
                    }
                    is NetworkState.Unavailable -> {
                        when (network) {
                            is Network.Unknown.WithoutLastNetwork -> {
                                { state.unknownWithoutLastNetwork(network.reason) }
                            }
                            is Network.Unknown.WithLastNetwork -> {
                                { state.unknownWithLastNetwork(network.lastKnownNetwork, network.reason) }
                            }
                            is Network.Known.Cellular -> state.availableWithCellular
                            is Network.Known.Wifi -> state.availableWithWifi
                            Network.Known.Absent -> state.remain()
                        }
                    }
                    is NetworkState.Unknown -> {
                        when (network) {
                            is Network.Unknown.WithoutLastNetwork -> state.remain()
                            is Network.Unknown.WithLastNetwork -> state.remain()
                            is Network.Known.Cellular -> state.availableWithCellular
                            is Network.Known.Wifi -> state.availableWithWifi
                            Network.Known.Absent -> state.unavailable
                        }
                    }
                }
            }
        }
    }
}

fun Flow<NetworkState>.network(): Flow<Network> {
    return map { it.networkType }.distinctUntilChanged()
}

fun Flow<NetworkState>.online(): Flow<Boolean> {
    return network().mapLatest {
        it is Network.Known.Wifi || it is Network.Known.Cellular
    }
}

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.system.network.state

expect class NetworkStateRepoBuilder : NetworkStateRepo.Builder

/*
 Copyright 2020 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.system.network.state

import com.splendo.kaluga.system.network.NetworkManagerBuilder

actual class NetworkStateRepoBuilder : NetworkStateRepo.Builder {
    override fun create(): NetworkStateRepo = NetworkStateRepo(NetworkManagerBuilder())
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.system.fragments

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.splendo.kaluga.architecture.viewmodel.KalugaViewModelFragment
import com.splendo.kaluga.example.R
import com.splendo.kaluga.example.databinding.FragmentNetworkBinding
import com.splendo.kaluga.example.shared.viewmodel.system.network.NetworkViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel

class NetworkFragment : KalugaViewModelFragment<NetworkViewModel>(R.layout.fragment_network) {

    override val viewModel: NetworkViewModel by viewModel()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding = FragmentNetworkBinding.inflate(inflater)
        binding.lifecycleOwner = this
        binding.viewModel = viewModel
        return binding.root
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.platformspecific.compose.bottomSheet

import androidx.compose.runtime.Composable
import com.splendo.kaluga.architecture.compose.viewModel.KalugaViewModelComposeActivity
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.example.platformspecific.compose.bottomSheet.ui.BottomSheetParentLayout

class BottomSheetActivity : KalugaViewModelComposeActivity<BaseViewModel>() {
    override val viewModel = BaseViewModel()

    @Composable
    override fun Layout(viewModel: BaseViewModel) {
        BottomSheetParentLayout()
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.platformspecific.compose.contacts

import androidx.compose.runtime.Composable
import com.splendo.kaluga.architecture.compose.viewModel.KalugaViewModelComposeActivity
import com.splendo.kaluga.architecture.viewmodel.BaseViewModel
import com.splendo.kaluga.example.platformspecific.compose.contacts.ui.ContactsLayout

class ContactsActivity : KalugaViewModelComposeActivity<BaseViewModel>() {
    override val viewModel = BaseViewModel()

    @Composable
    override fun Layout(viewModel: BaseViewModel) {
        ContactsLayout()
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.featureList

import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.observable.observableOf
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import com.splendo.kaluga.resources.localized

actual val showPlatformSpecificFeatures: Boolean = true

sealed class PlatformFeatureListNavigationAction : NavigationAction<Nothing>(null) {
    object ComposeNavigation : PlatformFeatureListNavigationAction()
    object ComposeBottomSheet : PlatformFeatureListNavigationAction()
}

sealed class PlatformFeature(val title: String) {
    object ComposeNavigation : PlatformFeature("feature_platform_specific_compose_navigation".localized())
    object ComposeBottomSheet : PlatformFeature("feature_platform_specific_compose_bottom_sheet".localized())
}

class PlatformSpecificFeaturesViewModel(
    navigator: Navigator<PlatformFeatureListNavigationAction>
) : NavigatingViewModel<PlatformFeatureListNavigationAction>(navigator) {

    val feature = observableOf(
        listOf(
            PlatformFeature.ComposeNavigation,
            PlatformFeature.ComposeBottomSheet
        )
    )

    fun onFeaturePressed(feature: PlatformFeature) {
        navigator.navigate(
            when (feature) {
                is PlatformFeature.ComposeNavigation -> PlatformFeatureListNavigationAction.ComposeNavigation
                is PlatformFeature.ComposeBottomSheet -> PlatformFeatureListNavigationAction.ComposeBottomSheet
            }
        )
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.viewmodel.featureList

actual val showPlatformSpecificFeatures: Boolean = false

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.platformspecific.compose.bottomSheet.ui

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.ModalBottomSheetState
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation
import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator
import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable
import com.splendo.kaluga.architecture.compose.viewModel.store
import com.splendo.kaluga.example.platformspecific.compose.bottomSheet.viewModel.bottomSheetNavigationRouteMapper
import com.splendo.kaluga.example.platformspecific.compose.contacts.ui.Padding
import com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet.BottomSheetViewModel

@Composable
fun BottomSheetLayout(contentNavHostController: NavHostController, sheetContentNavHostController: NavHostController, sheetState: ModalBottomSheetState) {
    val navigator = ModalBottomSheetNavigator(
        contentNavHostController,
        sheetContentNavHostController,
        sheetState,
        rememberCoroutineScope(),
        ::bottomSheetNavigationRouteMapper,
    )

    val viewModel = store {
        remember {
            BottomSheetViewModel(navigator)
        }
    }

    ViewModelComposable(viewModel) {
        HardwareBackButtonNavigation(onBackButtonClickHandler = { onClosePressed() })
        Column(
            Modifier
                .fillMaxWidth()
                .padding(Padding.default)
        ) {
            Text(text)
            Button(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(Padding.default),
                onClick = { onSubPagePressed() }
            ) {
                Text(buttonText)
            }
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.platformspecific.compose.bottomSheet.ui

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.ModalBottomSheetState
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.Text
import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.google.android.material.composethemeadapter.MdcTheme
import com.splendo.kaluga.architecture.compose.navigation.BottomSheetRouteController
import com.splendo.kaluga.architecture.compose.navigation.BottomSheetSheetContentRouteController
import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator
import com.splendo.kaluga.architecture.compose.navigation.NavHostRouteController
import com.splendo.kaluga.architecture.compose.navigation.NavigatingModalBottomSheetLayout
import com.splendo.kaluga.architecture.compose.navigation.route
import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable
import com.splendo.kaluga.architecture.compose.viewModel.store
import com.splendo.kaluga.example.platformspecific.compose.bottomSheet.viewModel.bottomSheetParentNavigationRouteMapper
import com.splendo.kaluga.example.platformspecific.compose.contacts.ui.Padding
import com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet.BottomSheetNavigation
import com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet.BottomSheetParentNavigation
import com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet.BottomSheetParentViewModel

@Composable
fun BottomSheetParentLayout() {
    MdcTheme {
        val bottomSheetRouteController = BottomSheetRouteController(
            NavHostRouteController(rememberNavController()),
            BottomSheetSheetContentRouteController(
                rememberNavController(),
                rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden),
                rememberCoroutineScope()
            )
        )

        bottomSheetRouteController.NavigatingModalBottomSheetLayout(
            sheetContent = { contentNavHostController, sheetContentNavHostController, sheetState ->
                composable(BottomSheetParentNavigation.ShowSheet.route()) {
                    BottomSheetLayout(contentNavHostController, sheetContentNavHostController, sheetState)
                }
                composable(BottomSheetNavigation.SubPage.route()) {
                    BottomSheetSubPageLayout(
                        contentNavHostController, sheetContentNavHostController, sheetState
                    )
                }
            },
            contentRoot = { contentNavHostController, sheetContentNavHostController, sheetState ->
                BottomSheetParentLayoutContent(contentNavHostController, sheetContentNavHostController, sheetState)
            },
            content = { contentNavHostController, _, _ ->
                composable(BottomSheetParentNavigation.SubPage.route()) {
                    BottomSheetParentSubPageLayout(contentNavHostController)
                }
            }
        )
    }
}

@Composable
fun BottomSheetParentLayoutContent(contentNavHostController: NavHostController, sheetNavHostController: NavHostController, sheetState: ModalBottomSheetState) {

    val navigator = ModalBottomSheetNavigator(
        NavHostRouteController(contentNavHostController),
        BottomSheetSheetContentRouteController(
            sheetNavHostController,
            sheetState,
            rememberCoroutineScope()
        ),
        ::bottomSheetParentNavigationRouteMapper
    )

    val viewModel = store {
        remember {
            BottomSheetParentViewModel(navigator)
        }
    }

    ViewModelComposable(viewModel) {
        Column(Modifier.fillMaxWidth()) {
            Button(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(Padding.default),
                onClick = { onShowSheetPressed() }
            ) {
                Text(sheetText)
            }
            Button(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(Padding.default),
                onClick = { onSubPagePressed() }
            ) {
                Text(subPageText)
            }
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.platformspecific.compose.bottomSheet.ui

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation
import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator
import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable
import com.splendo.kaluga.architecture.compose.viewModel.store
import com.splendo.kaluga.example.platformspecific.compose.bottomSheet.viewModel.bottomSheetParentSubPageNavigationRouteMapper
import com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet.BottomSheetParentSubPageViewModel

@Composable
fun BottomSheetParentSubPageLayout(navHostController: NavHostController) {
    val navigator = RouteNavigator(
        navHostController,
        ::bottomSheetParentSubPageNavigationRouteMapper
    )

    val viewModel = store {
        remember {
            BottomSheetParentSubPageViewModel(navigator)
        }
    }

    ViewModelComposable(viewModel) {
        HardwareBackButtonNavigation(onBackButtonClickHandler = { viewModel.onBackPressed() })
        Column(Modifier.fillMaxWidth()) {
            Text(text)
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.platformspecific.compose.bottomSheet.ui

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.ModalBottomSheetState
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation
import com.splendo.kaluga.architecture.compose.navigation.ModalBottomSheetNavigator
import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable
import com.splendo.kaluga.architecture.compose.viewModel.store
import com.splendo.kaluga.example.platformspecific.compose.bottomSheet.viewModel.bottomSheetSubPageNavigationRouteMapper
import com.splendo.kaluga.example.platformspecific.compose.contacts.ui.Padding
import com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet.BottomSheetSubPageViewModel

@Composable
fun BottomSheetSubPageLayout(
    contentNavHostController: NavHostController,
    sheetContentNavHostController: NavHostController,
    sheetState: ModalBottomSheetState
) {
    val navigator = ModalBottomSheetNavigator(
        contentNavHostController,
        sheetContentNavHostController,
        sheetState,
        rememberCoroutineScope(),
        ::bottomSheetSubPageNavigationRouteMapper
    )

    val viewModel = store {
        remember {
            BottomSheetSubPageViewModel(navigator)
        }
    }

    ViewModelComposable(viewModel) {
        HardwareBackButtonNavigation(onBackButtonClickHandler = { viewModel.onBackPressed() })
        Column(
            Modifier
                .fillMaxWidth()
                .padding(Padding.default)
        ) {
            Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
                Button(
                    modifier = Modifier.padding(Padding.default),
                    onClick = { onClosePressed() }
                ) {
                    Text("X")
                }
            }
            Text(text)
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.platformspecific.compose.bottomSheet.viewModel

import com.splendo.kaluga.architecture.compose.navigation.BottomSheetRoute
import com.splendo.kaluga.architecture.compose.navigation.Route
import com.splendo.kaluga.architecture.compose.navigation.bottomSheetContent
import com.splendo.kaluga.architecture.compose.navigation.bottomSheetSheetContent
import com.splendo.kaluga.architecture.compose.navigation.next
import com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet.BottomSheetNavigation
import com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet.BottomSheetParentNavigation
import com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet.BottomSheetParentSubPageNavigation
import com.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet.BottomSheetSubPageNavigation

/** Maps a navigation action to a route string. */
internal fun bottomSheetParentNavigationRouteMapper(action: BottomSheetParentNavigation): BottomSheetRoute {
    return when (action) {
        is BottomSheetParentNavigation.SubPage -> action.next.bottomSheetContent
        is BottomSheetParentNavigation.ShowSheet -> action.next.bottomSheetSheetContent
    }
}

/** Maps a navigation action to a route string. */
internal fun bottomSheetParentSubPageNavigationRouteMapper(action: BottomSheetParentSubPageNavigation): Route {
    return when (action) {
        is BottomSheetParentSubPageNavigation.Back -> Route.Back
    }
}

/** Maps a navigation action to a route string. */
internal fun bottomSheetNavigationRouteMapper(action: BottomSheetNavigation): BottomSheetRoute {
    return when (action) {
        is BottomSheetNavigation.Close -> Route.Close.bottomSheetSheetContent
        is BottomSheetNavigation.SubPage -> action.next.bottomSheetSheetContent
    }
}

/** Maps a navigation action to a route string. */
internal fun bottomSheetSubPageNavigationRouteMapper(action: BottomSheetSubPageNavigation): BottomSheetRoute {
    return when (action) {
        is BottomSheetSubPageNavigation.Close -> Route.Close.bottomSheetSheetContent
        is BottomSheetSubPageNavigation.Back -> Route.Back.bottomSheetSheetContent
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.platformspecific.compose.contacts.ui

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import com.splendo.kaluga.architecture.compose.navigation.HardwareBackButtonNavigation
import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator
import com.splendo.kaluga.architecture.compose.navigation.rememberCombinedNavigator
import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable
import com.splendo.kaluga.architecture.compose.viewModel.store
import com.splendo.kaluga.example.R
import com.splendo.kaluga.example.platformspecific.compose.contacts.viewModel.contactDetailsNavigationActivityMapper
import com.splendo.kaluga.example.platformspecific.compose.contacts.viewModel.contactDetailsNavigationRouteMapper
import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.model.ContactDetails
import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel.ContactDetailsNavigation
import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel.ContactDetailsViewModel

@Composable
fun ContactDetailsLayout(contactDetails: ContactDetails, navHostController: NavHostController) {
    val routeNavigator = RouteNavigator(
        navHostController,
        ::contactDetailsNavigationRouteMapper
    )

    val navigator = rememberCombinedNavigator { action: ContactDetailsNavigation<*> ->
        when (action) {
            is ContactDetailsNavigation.Close -> routeNavigator
            is ContactDetailsNavigation.SendEmail -> ::contactDetailsNavigationActivityMapper.toActivityNavigator()
        }
    }

    val viewModel = store {
        remember {
            ContactDetailsViewModel(contactDetails, navigator)
        }
    }
    ViewModelComposable(viewModel) {
        HardwareBackButtonNavigation(::back)

        Column {
            val image = painterResource(id = R.drawable.ic_account)
            Image(
                modifier = Modifier
                    .size(320.dp)
                    .align(CenterHorizontally),
                painter = image,
                contentDescription = ""
            )

            Column(
                modifier = Modifier.padding(horizontal = Padding.x2)
                    .fillMaxWidth()
            ) {
                Text(text = "Name:", style = MaterialTheme.typography.h6)
                Text(text = contactDetails.name, style = MaterialTheme.typography.body1)
                DefaultSpacer()

                Text(text = "Email:", style = MaterialTheme.typography.h6)
                Text(text = contactDetails.email, style = MaterialTheme.typography.body1)
                DefaultSpacer()

                Button(onClick = ::sendEmail) {
                    Text(text = sendEmailButtonText, style = MaterialTheme.typography.button)
                }
            }
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.platformspecific.compose.contacts.ui

import androidx.compose.runtime.Composable
import androidx.navigation.compose.rememberNavController
import com.google.android.material.composethemeadapter.MdcTheme
import com.splendo.kaluga.architecture.compose.navigation.RouteNavigator
import com.splendo.kaluga.architecture.compose.navigation.SetupNavHost
import com.splendo.kaluga.architecture.compose.navigation.composable
import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType
import com.splendo.kaluga.example.platformspecific.compose.contacts.viewModel.contactListNavigationRouteMapper
import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.model.ContactDetails
import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel.ContactsListNavigation

@Composable
fun ContactsLayout() {
    MdcTheme {
        val navigator = RouteNavigator(
            rememberNavController(),
            ::contactListNavigationRouteMapper
        )

        navigator.SetupNavHost(
            rootView = {
                ContactsListLayout(navigator = navigator)
            }
        ) { navHostController ->
            composable<ContactDetails, ContactsListNavigation.ShowContactDetails>(
                type = NavigationBundleSpecType.SerializedType(
                    ContactDetails.serializer()
                )
            ) { details ->
                ContactDetailsLayout(details, navHostController)
            }
        }
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.platformspecific.compose.contacts.ui

import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.splendo.kaluga.architecture.compose.state
import com.splendo.kaluga.architecture.compose.viewModel.ViewModelComposable
import com.splendo.kaluga.architecture.compose.viewModel.store
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.example.R
import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.model.ContactDetails
import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel.ContactsListNavigation
import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel.ContactsListViewModel

@Composable
fun ContactsListLayout(navigator: Navigator<ContactsListNavigation<*>>) {
    val viewModel = store {
        remember {
            ContactsListViewModel(navigator)
        }
    }

    ViewModelComposable(viewModel) {
        val items by contacts.state()
        LazyColumn(modifier = Modifier.padding(Padding.default)) {
            items(items) { contactDetails ->
                ListItem(contactDetails) {
                    onContactClick(contactDetails)
                }

                DefaultSpacer()
            }
        }
    }
}

@Composable
private fun ListItem(
    contactDetails: ContactDetails,
    onClickHandler: () -> Unit
) {
    Row(
        modifier = Modifier
            .clickable(onClick = onClickHandler)
            .fillMaxWidth()
    ) {
        val image = painterResource(id = R.drawable.ic_account)
        Image(
            modifier = Modifier.size(48.dp),
            painter = image,
            contentDescription = ""
        )

        DefaultSpacer()

        Text(
            modifier = Modifier.align(CenterVertically),
            text = contactDetails.name,
            style = MaterialTheme.typography.h5
        )
    }
}

@Composable
@Preview
private fun ListItemPreview() {
    ListItem(ContactDetails("name", "email")) { }
}

package com.splendo.kaluga.example.platformspecific.compose.contacts.ui

import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

object Padding {
    val default = 8.dp
    val x2 = 16.dp
}

@Composable
fun DefaultSpacer() {
    Spacer(modifier = Modifier.size(Padding.default))
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.platformspecific.compose.contacts.viewModel

import com.splendo.kaluga.architecture.compose.navigation.Route
import com.splendo.kaluga.architecture.compose.navigation.next
import com.splendo.kaluga.architecture.navigation.NavigationSpec
import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel.ContactDetailsNavigation
import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel.ContactsListNavigation

/** Maps a navigation action to a route string. */
internal fun contactListNavigationRouteMapper(action: ContactsListNavigation<*>): Route {
    return when (action) {
        is ContactsListNavigation.ShowContactDetails -> action.next
    }
}

/** Maps a navigation action to a route string. */
internal fun contactDetailsNavigationRouteMapper(action: ContactDetailsNavigation<*>): Route {
    return when (action) {
        is ContactDetailsNavigation.Close -> Route.Back
        else -> throw IllegalStateException("Unsupported action: ${action::class.simpleName}")
    }
}

/** Maps a navigation action to a NavigationSpec. */
internal fun contactDetailsNavigationActivityMapper(action: ContactDetailsNavigation<*>): NavigationSpec {
    return when (action) {
        is ContactDetailsNavigation.SendEmail -> NavigationSpec.Email(
            emailSettings = NavigationSpec.Email.EmailSettings(
                type = NavigationSpec.Email.Type.Plain,
                to = listOf(action.bundle!!.get(action.type))
            )
        )
        else -> throw IllegalStateException("Unsupported action: ${action::class.simpleName}")
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet

import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import com.splendo.kaluga.resources.localized

sealed class BottomSheetParentSubPageNavigation : NavigationAction<Nothing>(null) {
    object Back : BottomSheetParentSubPageNavigation()
}

class BottomSheetParentSubPageViewModel(navigator: Navigator<BottomSheetParentSubPageNavigation>) : NavigatingViewModel<BottomSheetParentSubPageNavigation>(navigator) {

    val text = "bottom_sheet_sub_page_title".localized()

    fun onBackPressed() {
        navigator.navigate(BottomSheetParentSubPageNavigation.Back)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet

import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import com.splendo.kaluga.resources.localized

sealed class BottomSheetParentNavigation : NavigationAction<Nothing>(null) {
    object ShowSheet : BottomSheetParentNavigation()
    object SubPage : BottomSheetParentNavigation()
}

class BottomSheetParentViewModel(navigator: Navigator<BottomSheetParentNavigation>) : NavigatingViewModel<BottomSheetParentNavigation>(navigator) {

    val sheetText = "bottom_sheet_show_sheet".localized()
    val subPageText = "bottom_sheet_show_sub_page".localized()

    fun onShowSheetPressed() {
        navigator.navigate(BottomSheetParentNavigation.ShowSheet)
    }

    fun onSubPagePressed() {
        navigator.navigate(BottomSheetParentNavigation.SubPage)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet

import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import com.splendo.kaluga.resources.localized

sealed class BottomSheetSubPageNavigation : NavigationAction<Nothing>(null) {
    object Back : BottomSheetSubPageNavigation()
    object Close : BottomSheetSubPageNavigation()
}

class BottomSheetSubPageViewModel(navigator: Navigator<BottomSheetSubPageNavigation>) : NavigatingViewModel<BottomSheetSubPageNavigation>(navigator) {

    val text = "bottom_sheet_sheet_sub_page_title".localized()

    fun onBackPressed() {
        navigator.navigate(BottomSheetSubPageNavigation.Back)
    }

    fun onClosePressed() {
        navigator.navigate(BottomSheetSubPageNavigation.Close)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.platformspecific.compose.bottomSheet

import com.splendo.kaluga.architecture.navigation.NavigationAction
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import com.splendo.kaluga.resources.localized

sealed class BottomSheetNavigation : NavigationAction<Nothing>(null) {
    object Close : BottomSheetNavigation()
    object SubPage : BottomSheetNavigation()
}

class BottomSheetViewModel(navigator: Navigator<BottomSheetNavigation>) : NavigatingViewModel<BottomSheetNavigation>(navigator) {

    val text = "bottom_sheet_sheet_content".localized()
    val buttonText = "bottom_sheet_show_sub_page".localized()

    fun onSubPagePressed() {
        navigator.navigate(BottomSheetNavigation.SubPage)
    }

    fun onClosePressed() {
        navigator.navigate(BottomSheetNavigation.Close)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.platformspecific.compose.contacts.model

import kotlinx.serialization.Serializable

@Serializable
data class ContactDetails(val name: String, val email: String)

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel

import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.model.ContactDetails

sealed class ContactDetailsNavigation<T>(value: T, type: NavigationBundleSpecType<T>) : SingleValueNavigationAction<T>(value, type) {
    class SendEmail(email: String) : ContactDetailsNavigation<String>(
        email,
        NavigationBundleSpecType.StringType
    )
    object Close : ContactDetailsNavigation<Unit>(Unit, NavigationBundleSpecType.UnitType)
}

class ContactDetailsViewModel(
    val contactDetails: ContactDetails,
    navigator: Navigator<ContactDetailsNavigation<*>>
) : NavigatingViewModel<ContactDetailsNavigation<*>>(navigator) {

    val sendEmailButtonText = "Send email"

    fun sendEmail() {
        navigator.navigate(ContactDetailsNavigation.SendEmail(contactDetails.email))
    }

    fun back() {
        navigator.navigate(ContactDetailsNavigation.Close)
    }
}

/*
 Copyright 2021 Splendo Consulting B.V. The Netherlands

    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.splendo.kaluga.example.shared.platformspecific.compose.contacts.viewModel

import com.splendo.kaluga.architecture.navigation.NavigationBundleSpecType
import com.splendo.kaluga.architecture.navigation.Navigator
import com.splendo.kaluga.architecture.navigation.SingleValueNavigationAction
import com.splendo.kaluga.architecture.observable.FlowInitializedObservable
import com.splendo.kaluga.architecture.observable.toInitializedObservable
import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel
import com.splendo.kaluga.example.shared.platformspecific.compose.contacts.model.ContactDetails
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch

sealed class ContactsListNavigation<T>(value: T, type: NavigationBundleSpecType<T>) : SingleValueNavigationAction<T>(value, type) {
    data class ShowContactDetails(val contactDetails: ContactDetails) : ContactsListNavigation<ContactDetails>(
        contactDetails,
        NavigationBundleSpecType.SerializedType(ContactDetails.serializer())
    )
}

class ContactsListViewModel(
    navigator: Navigator<ContactsListNavigation<*>>
) : NavigatingViewModel<ContactsListNavigation<*>>(navigator) {
    val contacts: FlowInitializedObservable<List<ContactDetails>> =
        MutableStateFlow(emptyList<ContactDetails>())
            .also {
                coroutineScope.launch {
                    it.value = loadContacts()
                }
            }.toInitializedObservable(coroutineScope)

    private suspend fun loadContacts(): List<ContactDetails> {
        // a real call shall happen here
        return listOf("Alice", "Bob", "Charlie", "David").map { name ->
            ContactDetails(name = name, email = "${name.lowercase()}@example.com")
        }
    }

    fun onContactClick(contactDetails: ContactDetails) {
        navigator.navigate(
            ContactsListNavigation.ShowContactDetails(contactDetails)
        )
    }
}