//
// Copyright (c) 2010-2018 Antmicro
//
// This file is licensed under the MIT License.
// Full license text is available in 'licenses/MIT.txt'.
//
using System.Collections.Generic;
using Antmicro.Renode.Peripherals.Bus;
using Antmicro.Renode.Core.Structure.Registers;
using Antmicro.Renode.Core;
using Antmicro.Renode.Logging;

namespace Antmicro.Renode.Peripherals.UART
{
    public class ESP32_UART : UARTBase, IDoubleWordPeripheral, IBytePeripheral, IKnownSize
    {
        public ESP32_UART(Machine machine) : base(machine)
        {
            IRQ = new GPIO();
            var registersMap = new Dictionary<long, DoubleWordRegister>
            {
                {(long)Registers.FIFO, new DoubleWordRegister(this)
                    .WithValueField(0, 8, writeCallback: (_, value) => this.TransmitCharacter((byte)value),
                        valueProviderCallback: _ => {
                            if(!TryGetCharacter(out var character))
                            {
                                this.Log(LogLevel.Warning, "Trying to read from an empty FIFO.");
                            }
                            return character;
                        })
                },
                {(long)Registers.INT_ENA, new DoubleWordRegister(this)
                    .WithFlag(0, FieldMode.Write, name: "UART_RXFIFO_FULL_INT_ENA")
                },
                {(long)Registers.INT_CLR, new DoubleWordRegister(this)
                    .WithValueField(0, 32, FieldMode.Write) // Temporary hack
                },
                {(long)Registers.RX_FILT, new DoubleWordRegister(this)
                    .WithValueField(0, 8, FieldMode.Read | FieldMode.Write, name: "GLITCH_FILT") // when input pulse width is lower than this value, the pulse is ignored.
                    .WithFlag(8,  FieldMode.Read | FieldMode.Write, name: "GLITCH_FILT_EN") // Set this bit to enable Rx signal filter.
                },
                {(long)Registers.STATUS, new DoubleWordRegister(this)
                    .WithValueField(0, 10, FieldMode.Read, name: "RXFIFO_CNT")
                    .WithFlag(13, FieldMode.Read, name: "UART_DSRN")
                    .WithFlag(14, FieldMode.Read, valueProviderCallback: (_) => true, name: "UART_CTSN")
                    .WithFlag(15, FieldMode.Read, valueProviderCallback: (_) => true, name: "UART_RXD")
                    .WithValueField(16, 10, FieldMode.Read, name: "TXFIFO_CNT")
                    .WithFlag(29, FieldMode.Read, valueProviderCallback: (_) => true, name: "UART_DTRN")
                    .WithFlag(30, FieldMode.Read, valueProviderCallback: (_) => true, name: "UART_RTSN")
                    .WithFlag(31, FieldMode.Read, valueProviderCallback: (_) => true, name: "UART_TXD")
                },
                {(long)Registers.CONF0, new DoubleWordRegister(this)
                    .WithFlag(0, FieldMode.Write, name: "UART_PARITY")
                    .WithFlag(1, FieldMode.Write, name: "UART_PARITY_EN")
                    .WithValueField(2, 2, FieldMode.Read, name: "UART_BIT_NUM")
                    .WithValueField(4, 2, FieldMode.Read, name: "UART_STOPBIT_NUM")
                    .WithFlag(17, FieldMode.Write, name: "UART_RXFIFO_RST")
                    .WithFlag(18, FieldMode.Write, name: "UART_TXFIFO_RST")
                    .WithFlag(26, FieldMode.Write, name: "UART_ERR_WR_MASK")
                    .WithFlag(27, FieldMode.Write, name: "UART_AUTOBAUD_EN")
                },
                {(long)Registers.CONF1, new DoubleWordRegister(this)
                    .WithValueField(0, 10, FieldMode.Read | FieldMode.Write, name: "RXFIFO_FULL_THRHD") // It will produce rxfifo_full_int interrupt when receiver receives more data than this register value.
                    .WithValueField(10, 10, FieldMode.Read | FieldMode.Write, name: "TXFIFO_EMPTY_THRHD") // It will produce txfifo_empty_int interrupt when the data amount in Tx-FIFO is less than this register value.
                    .WithFlag(20,  FieldMode.Read | FieldMode.Write, name: "DIS_RX_DAT_OVF") // Disable UART Rx data overflow detect.
                    .WithFlag(21,  FieldMode.Read | FieldMode.Write, name: "RX_TOUT_FLOW_DIS") // Set this bit to stop accumulating idle_cnt when hardware flow control works.
                    .WithFlag(22,  FieldMode.Read | FieldMode.Write, name: "RX_FLOW_EN") // This is the flow enable bit for UART receiver.
                    .WithFlag(23,  FieldMode.Read | FieldMode.Write, name: "RX_TOUT_EN") // This is the enble bit for uart receiver's timeout function.
                },
                {(long)Registers.RXD_CNT, new DoubleWordRegister(this)
                    .WithValueField(0, 10, FieldMode.Read, name: "RXD_EDGE_CNT") // This register stores the count of rxd edge change. It is used in baud rate-detect process.
                },
                {(long)Registers.CLKDIV, new DoubleWordRegister(this)
                    .WithValueField(0, 12, FieldMode.Read | FieldMode.Write, name: "CLKDIV") // Clock divider configuration
                    .WithValueField(20, 4, FieldMode.Read | FieldMode.Write, name: "FRAG") // The decimal part of the frequency divider factor.
                },
                {(long)Registers.CLK_CONF, new DoubleWordRegister(this)
                    .WithValueField(0, 6, FieldMode.Read | FieldMode.Write, name: "SCLK_DIV_B") // The  denominator of the frequency divider factor.
                    .WithValueField(6, 6, FieldMode.Read | FieldMode.Write, name: "SCLK_DIV_A") // The numerator of the frequency divider factor.
                    .WithValueField(12, 8, FieldMode.Read | FieldMode.Write, name: "SCLK_DIV_NUM") // The integral part of the frequency divider factor.
                    .WithValueField(20, 2, FieldMode.Read | FieldMode.Write, name: "SCLK_SEL") // UART clock source select. 1: 80Mhz, 2: 8Mhz, 3: XTAL.
                    .WithFlag(22,  FieldMode.Read | FieldMode.Write, name: "SCLK_EN") // Set this bit to enable UART Tx/Rx clock.
                    .WithFlag(23,  FieldMode.Read | FieldMode.Write, name: "RST_CORE") // Write 1 then write 0 to this bit, reset UART Tx/Rx.
                    .WithFlag(24,  FieldMode.Read | FieldMode.Write, name: "TX_SCLK_EN") // Set this bit to enable UART Tx clock.
                    .WithFlag(25,  FieldMode.Read | FieldMode.Write, name: "RX_SCLK_EN") // Set this bit to enable UART Rx clock.
                    .WithFlag(26,  FieldMode.Read | FieldMode.Write, name: "TX_RST_CORE") // Write 1 then write 0 to this bit, reset UART Tx.
                    .WithFlag(27,  FieldMode.Read | FieldMode.Write, name: "RX_RST_CORE") // Write 1 then write 0 to this bit, reset UART Rx.
                },
            };

            registers = new DoubleWordRegisterCollection(this, registersMap);
        }

        public uint ReadDoubleWord(long offset)
        {
            return registers.Read(offset);
        }

        public byte ReadByte(long offset)
        {
            if (offset % 4 != 0)
            {
                // in the current configuration, only the lowest byte
                // contains a meaningful data
                return 0;
            }
            return (byte)ReadDoubleWord(offset);
        }

        public override void Reset()
        {
            base.Reset();
            registers.Reset();

            UpdateInterrupts();
        }

        public void WriteDoubleWord(long offset, uint value)
        {
            registers.Write(offset, value);
        }

        public void WriteByte(long offset, byte value)
        {
            if (offset % 4 != 0)
            {
                // in the current configuration, only the lowest byte
                // contains a meaningful data
                return;
            }

            WriteDoubleWord(offset, value);
        }

        public long Size => 0x100;

        public GPIO IRQ { get; private set; }

        public override Bits StopBits => Bits.One;

        public override Parity ParityBit => Parity.None;

        public override uint BaudRate => 115200;

        protected override void CharWritten()
        {
            UpdateInterrupts();
        }

        protected override void QueueEmptied()
        {
            UpdateInterrupts();
        }

        private void UpdateInterrupts()
        {
            IRQ.Set(false);
        }

        private IFlagRegisterField rxEventEnabled;
        private IFlagRegisterField rxEventPending;
        private readonly DoubleWordRegisterCollection registers;

        private enum Registers : long
        {
            // FIFO Configuration
            FIFO = 0x00,
            MEM_CONF = 0x60,

            // UART interrupt register
            INT_RAW = 0x04,
            INT_ST = 0x08,
            INT_ENA = 0x0c,
            INT_CLR = 0x10,

            // Configuration register
            CLKDIV = 0x14,
            RX_FILT = 0x18,
            CONF0 = 0x20,
            CONF1 = 0x24,
            FLOW_CONF = 0x34,
            SLEEP_CONF = 0x38,
            SWFC_CONF0 = 0x3c,
            SWFC_CONF1 = 0x40,
            TXBRK_CONF = 0x44,
            IDLE_CONF = 0x48,
            RS485_CONF = 0x4c,
            CLK_CONF = 0x78,

            // Status register
            STATUS = 0x1c,
            MEM_TX_STATUS = 0x64,
            MEM_RX_STATUS = 0x68,
            FSM_STATUS = 0x6c,

            // Autobaud register
            LOWPULSE = 0x28,
            HIGHPULSE = 0x2c,
            RXD_CNT = 0x30,
            POSPULSE = 0x70,
            NEGPULSE = 0x74,

            // Escape sequence selection configuration
            AT_CMD_PRECNT = 0x50,
            AT_CMD_POSTCNT = 0x54,
            AT_CMD_GAPTOUT = 0x58,
            AT_CMD_CHAR = 0x5c,

            // Version
            DATE = 0x7c,
            ID = 0x80,
        }
    }
}
