package dev.kaderli.magsend.activity

import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat
import dev.kaderli.magsend.R
import dev.kaderli.magsend.model.Sample
import dev.kaderli.magsend.model.Signal
import java.lang.Integer.min

class ReceiveActivity : BaseSensorActivity() {
  private lateinit var preambleStatus: TextView
  private lateinit var headerStatus: TextView
  private lateinit var receiveValue: TextView

  /**
   * List that contains all received samples from the sensor.
   */
  private var samples = ArrayList<Sample<Float>>()

  /**
   * A list that contains the samples as high or low signals.
   */
  private var signal = ArrayList<Sample<Signal>>()

  /**
   * Flag that determines if the preamble was already received.
   */
  private var preambleReceived = false

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    preambleStatus = findViewById(R.id.preambleStatus)
    headerStatus = findViewById(R.id.headerStatus)
    receiveValue = findViewById(R.id.receiveValue)
  }

  override fun getLayoutId(): Int {
    return R.layout.activity_receive
  }

  private fun getRangeOfData(): Float {
    val maxValue = samples.maxByOrNull { it.value } ?: Sample(0f)
    val minValue = samples.minByOrNull { it.value } ?: Sample(0f)
    //Log.i(TAG, "Min: ${minValue}, Max: $maxValue")
    return maxValue.value - minValue.value
  }

  private fun getThresholdForHighSignals(): Float {
    if (getRangeOfData() < 3) {
      return getMidRangeOfData() + 3
    }

    return getMidRangeOfData() + 2
  }

  private fun getThresholdForLowSignals(): Float {
    if (getRangeOfData() < 3) {
      return getMidRangeOfData() + 3
    }

    return getMidRangeOfData() - 2
  }

  private fun getMidRangeOfData(): Float {
    val maxValue = samples.maxByOrNull { it.value } ?: Sample(0f)
    val minValue = samples.minByOrNull { it.value } ?: Sample(0f)
    return (maxValue.value + minValue.value) / 2
  }

  private fun isHigh(value: Float): Boolean {
    return value > getThresholdForHighSignals()
  }

  private fun isLow(value: Float): Boolean {
    return value < getThresholdForLowSignals()
  }

  /**
   * Check if the given sample should count as a high or low signal. If the sample is inside
   * the threshold area it is not counted.
   */
  private fun getSignalForSample(sample: Sample<Float>): Sample<Signal>? {
    if (isHigh(sample.value)) {
      return Sample(Signal.High)
    }

    if (isLow(sample.value)) {
      return Sample(Signal.Low)
    }

    return null
  }

  /**
   * Attempts to detect the preamble in the already received signal. If it can find the preamble it returns true and
   * false otherwise.
   */
  private fun detectPreamble(): Boolean {
    val cleanedSignal = cleanSignal(signal)
    val compressedSignal = compressSignal(cleanedSignal)

    if (compressedSignal.size < 4) {
      return false
    }

    for (i in 0..compressedSignal.size - 4) {
      val firstValue = compressedSignal[i]
      val secondValue = compressedSignal[i + 1]
      val thirdValue = compressedSignal[i + 2]
      val fourthValue = compressedSignal[i + 3]

      if (firstValue.value != Signal.High || secondValue.value != Signal.Low || thirdValue.value != Signal.High) {
        continue
      }

      if (secondValue.timestamp - firstValue.timestamp > 2900 && thirdValue.timestamp - secondValue.timestamp > 2900) {
        return true
      }
    }

    return false
  }

  /**
   * This functions takes the signal and filters out samples that are only in a cluster of 3 or less of the same signal.
   * This way we can remove single jumps that occur in the signal.
   */
  private fun cleanSignal(signal: List<Sample<Signal>>): List<Sample<Signal>> {
    val cleanSignal = signal.filterIndexed { index, sample ->
      var elementCount = 1

      var previousIndex = index - 1
      while (previousIndex > 0 && signal[previousIndex].value == sample.value) {
        elementCount++
        previousIndex--
      }

      var nextIndex = index + 1
      while (nextIndex < signal.size && signal[nextIndex].value == sample.value) {
        elementCount++
        nextIndex++
      }

      elementCount > 3
    }

    return cleanSignal
  }

  private fun compressSignal(signal: List<Sample<Signal>>): List<Sample<Signal>> {
    // Only keep the first of consecutive same signals
    val compactSignal = ArrayList<Sample<Signal>>()
    signal.forEach { sample ->
      if (sample.value != compactSignal.lastOrNull()?.value) {
        compactSignal.add(sample)
      }
    }

    return compactSignal
  }

  private fun analyzePacketSignal(): List<Int> {
    val cleanedSignal = cleanSignal(signal)

    var maxSamplesInARow = 0
    var count = 0
    cleanedSignal.forEachIndexed { index, sample ->
      if(index == 0) {
        return@forEachIndexed
      }

      if(sample.value == cleanedSignal[index - 1].value) {
        count ++
        if(count > maxSamplesInARow) {
          maxSamplesInARow = count
        }
      } else {
        count = 0
      }
    }


    val packetData = ArrayList<Int>()
    var lastSignal: Sample<Signal>? = null
    var isFirstSignal = true
    var samplesSinceLastSignal = 0
    cleanedSignal.forEach { sample ->
      if (lastSignal == null) {
        lastSignal = sample
      }

      if (sample.value != lastSignal!!.value) {
        if(samplesSinceLastSignal > (maxSamplesInARow * 0.75) || isFirstSignal) {
          //Log.i(TAG, "$samplesSinceLastSignal")
          if (sample.value == Signal.High) {
            packetData.add(0)
          } else {
            packetData.add(1)
          }
          isFirstSignal = false
          samplesSinceLastSignal=0
        }
        lastSignal = sample
      }
      samplesSinceLastSignal++
    }


    //Log.i(TAG, "$cleanSignal")

    return packetData
  }

  override fun sensorValueReceived(magneticFieldStrength: Float) {
    // Add the current received sensor value to the samples
    samples.add(Sample(magneticFieldStrength))

    // Determine whether the sample is a high or low signal
    val currentSignal = getSignalForSample(samples.last()) ?: return
    signal.add(currentSignal)

    // Try to detect the preamble in the signal
    if (!preambleReceived) {
      // If we detect the preamble we change the status on the screen and clear the existing signal data.
      if (detectPreamble()) {
        preambleReceived = true
        preambleStatus.setText(R.string.preamble_status_detected)
        preambleStatus.setTextColor(ContextCompat.getColor(this, R.color.success))
        signal.clear()
        return
      }
    }

    // After the preamble is detected we attempt to decode the packet.
    if (preambleReceived) {
      // Get the decoded packet data
      val packet = analyzePacketSignal()

      // Get the payload length of in the data
      var payloadLength = 0
      if (packet.size >= 4) {
        payloadLength = listToInteger(packet.take(4))
        headerStatus.text = getString(R.string.payload_length, payloadLength)
      }

      if (packet.size >= 4 + (8)) {
        val numberOfAvailablePayloadBytes = min((packet.size - 4) / 8, payloadLength)
        var payload = ""
        for (i in 1..numberOfAvailablePayloadBytes) {
          payload += listToInteger(packet.take(4 + (8 * i)).takeLast(8)).toChar().toString()
        }

        receiveValue.text = payload

        // TODO: CRC show and calculate if it's correct
        if (packet.size >= 4 + (8 * payloadLength) + 8) {
          // CRC Check
          val crc = listToInteger(packet.take(4 + (8 * payloadLength) + 8).takeLast(8))
          Log.i(TAG, "CRC: $crc")
        }
      }
    }
  }

  private fun listToInteger(values: List<Int>): Int {
    var value = 0

    for (bit in values) {
      value = (value shl 1) + bit
    }

    return value
  }


  fun restartReceiveProcess(view: View) {
    preambleReceived = false
    preambleStatus.setText(R.string.preamble_status_not_detected)
    preambleStatus.setTextColor(ContextCompat.getColor(this, R.color.error))
    samples.clear()
    signal.clear()
  }
}
