/**
 * DesfireFileSettings.kt
 *
 * Copyright (C) 2011 Eric Butler
 * Copyright (C) 2019  <seil0@mosad.xyz>
 *
 * Authors:
 * Eric Butler <eric@codebutler.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.codebutler.farebot.card.desfire

import android.os.Parcel
import android.os.Parcelable
import com.codebutler.farebot.Utils
import org.apache.commons.lang3.ArrayUtils
import java.io.ByteArrayInputStream

abstract class DesfireFileSettings : Parcelable {
    private val fileType: Byte
    private val commSetting: Byte
    private val accessRights: ByteArray

    @Suppress("unused")
    val fileTypeName: String
        get() {
            return when (fileType) {
                STANDARD_DATA_FILE -> "Standard"
                BACKUP_DATA_FILE -> "Backup"
                VALUE_FILE -> "Value"
                LINEAR_RECORD_FILE -> "Linear Record"
                CYCLIC_RECORD_FILE -> "Cyclic Record"
                else -> "Unknown"
            }
        }

    private constructor(stream: ByteArrayInputStream) {
        fileType = stream.read().toByte()
        commSetting = stream.read().toByte()

        accessRights = ByteArray(2)
        stream.read(accessRights, 0, accessRights.size)
    }

    private constructor(fileType: Byte, commSetting: Byte, accessRights: ByteArray) {
        this.fileType = fileType
        this.commSetting = commSetting
        this.accessRights = accessRights
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeByte(fileType)
        parcel.writeByte(commSetting)
        parcel.writeInt(accessRights.size)
        parcel.writeByteArray(accessRights)
    }

    override fun describeContents(): Int {
        return 0
    }

    class StandardDesfireFileSettings : DesfireFileSettings {
        private val fileSize: Int

        internal constructor(stream: ByteArrayInputStream) : super(stream) {
            val buf = ByteArray(3)
            stream.read(buf, 0, buf.size)
            ArrayUtils.reverse(buf)
            fileSize = Utils.byteArrayToInt(buf)
        }

        internal constructor(
            fileType: Byte,
            commSetting: Byte,
            accessRights: ByteArray,
            fileSize: Int
        ) : super(fileType, commSetting, accessRights) {
            this.fileSize = fileSize
        }

        override fun writeToParcel(parcel: Parcel, flags: Int) {
            super.writeToParcel(parcel, flags)
            parcel.writeInt(fileSize)
        }
    }

    class RecordDesfireFileSettings : DesfireFileSettings {
        private val maxRecords: Int
        val recordSize: Int
        val curRecords: Int

        constructor(stream: ByteArrayInputStream) : super(stream) {

            var buf = ByteArray(3)
            stream.read(buf, 0, buf.size)
            ArrayUtils.reverse(buf)
            recordSize = Utils.byteArrayToInt(buf)

            buf = ByteArray(3)
            stream.read(buf, 0, buf.size)
            ArrayUtils.reverse(buf)
            maxRecords = Utils.byteArrayToInt(buf)

            buf = ByteArray(3)
            stream.read(buf, 0, buf.size)
            ArrayUtils.reverse(buf)
            curRecords = Utils.byteArrayToInt(buf)
        }

        internal constructor(
            fileType: Byte,
            commSetting: Byte,
            accessRights: ByteArray,
            recordSize: Int,
            maxRecords: Int,
            curRecords: Int
        ) : super(fileType, commSetting, accessRights) {
            this.recordSize = recordSize
            this.maxRecords = maxRecords
            this.curRecords = curRecords
        }

        override fun writeToParcel(parcel: Parcel, flags: Int) {
            super.writeToParcel(parcel, flags)
            parcel.writeInt(recordSize)
            parcel.writeInt(maxRecords)
            parcel.writeInt(curRecords)
        }
    }


    class ValueDesfireFileSettings(stream: ByteArrayInputStream) : DesfireFileSettings(stream) {
        private val lowerLimit: Int
        private val upperLimit: Int
        val value: Int
        private val limitedCreditEnabled: Byte

        init {

            var buf = ByteArray(4)
            stream.read(buf, 0, buf.size)
            ArrayUtils.reverse(buf)
            lowerLimit = Utils.byteArrayToInt(buf)

            buf = ByteArray(4)
            stream.read(buf, 0, buf.size)
            ArrayUtils.reverse(buf)
            upperLimit = Utils.byteArrayToInt(buf)

            buf = ByteArray(4)
            stream.read(buf, 0, buf.size)
            ArrayUtils.reverse(buf)
            value = Utils.byteArrayToInt(buf)


            buf = ByteArray(1)
            stream.read(buf, 0, buf.size)
            limitedCreditEnabled = buf[0]

            //http://www.skyetek.com/docs/m2/desfire.pdf
            //http://neteril.org/files/M075031_desfire.pdf
        }

        override fun writeToParcel(parcel: Parcel, flags: Int) {
            super.writeToParcel(parcel, flags)
            parcel.writeInt(lowerLimit)
            parcel.writeInt(upperLimit)
            parcel.writeInt(value)
            parcel.writeByte(limitedCreditEnabled)
        }
    }

    class UnsupportedDesfireFileSettings(fileType: Byte) :
        DesfireFileSettings(fileType, java.lang.Byte.MIN_VALUE, ByteArray(0))

    companion object {

        /* DesfireFile Types */
        internal const val STANDARD_DATA_FILE = 0x00.toByte()
        internal const val BACKUP_DATA_FILE = 0x01.toByte()
        internal const val VALUE_FILE = 0x02.toByte()
        internal const val LINEAR_RECORD_FILE = 0x03.toByte()
        internal const val CYCLIC_RECORD_FILE = 0x04.toByte()

        @Throws(DesfireException::class)
        fun create(data: ByteArray): DesfireFileSettings {
            val fileType = data[0]

            val stream = ByteArrayInputStream(data)

            return if (fileType == STANDARD_DATA_FILE || fileType == BACKUP_DATA_FILE)
                StandardDesfireFileSettings(stream)
            else if (fileType == LINEAR_RECORD_FILE || fileType == CYCLIC_RECORD_FILE)
                RecordDesfireFileSettings(stream)
            else if (fileType == VALUE_FILE)
                ValueDesfireFileSettings(stream)
            else
                throw DesfireException("Unknown file type: " + Integer.toHexString(fileType.toInt()))
        }

        @Suppress("unused")
        @JvmField
        val CREATOR: Parcelable.Creator<DesfireFileSettings> = object : Parcelable.Creator<DesfireFileSettings> {
            override fun createFromParcel(source: Parcel): DesfireFileSettings {
                val fileType = source.readByte()
                val commSetting = source.readByte()
                val accessRights = ByteArray(source.readInt())
                source.readByteArray(accessRights)

                if (fileType == STANDARD_DATA_FILE || fileType == BACKUP_DATA_FILE) {
                    val fileSize = source.readInt()
                    return StandardDesfireFileSettings(fileType, commSetting, accessRights, fileSize)
                } else if (fileType == LINEAR_RECORD_FILE || fileType == CYCLIC_RECORD_FILE) {
                    val recordSize = source.readInt()
                    val maxRecords = source.readInt()
                    val curRecords = source.readInt()
                    return RecordDesfireFileSettings(
                        fileType,
                        commSetting,
                        accessRights,
                        recordSize,
                        maxRecords,
                        curRecords
                    )
                } else {
                    return UnsupportedDesfireFileSettings(fileType)
                }
            }

            override fun newArray(size: Int): Array<DesfireFileSettings?> {
                return arrayOfNulls(size)
            }
        }
    }
}