# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
"""
Generates the documentation for the register tool

"""
from reggen.access import SWACCESS_PERMITTED, HWACCESS_PERMITTED
from reggen import (validate,
                    ip_block, enum_entry, field,
                    register, multi_register, window)

from typing import Any, Optional, TextIO


def genout(outfile: TextIO, msg: str) -> None:
    outfile.write(msg)


doc_intro = """

<!-- Start of output generated by `regtool.py --doc` -->

The tables describe each key and the type of the value. The following
types are used:

Type | Description
---- | -----------
"""

swaccess_intro = """

Register fields are tagged using the swaccess key to describe the
permitted access and side-effects. This key must have one of these
values:

"""

hwaccess_intro = """

Register fields are tagged using the hwaccess key to describe the
permitted access from hardware logic and side-effects. This key must
have one of these values:

"""

top_example = """
The basic structure of a register definition file is thus:

```hjson
{
  name: "GP",
  regwidth: "32",
  registers: [
    // register definitions...
  ]
}

```

"""

register_example = """

The basic register definition group will follow this pattern:

```hjson
    { name: "REGA",
      desc: "Description of register",
      swaccess: "rw",
      resval: "42",
      fields: [
        // bit field definitions...
      ]
    }
```

The name and brief description are required. If the swaccess key is
provided it describes the access pattern that will be used by all
bitfields in the register that do not override with their own swaccess
key. This is a useful shortcut because in most cases a register will
have the same access restrictions for all fields. The reset value of
the register may also be provided here or in the individual fields. If
it is provided in both places then they must match, if it is provided
in neither place then the reset value defaults to zero for all except
write-only fields when it defaults to x.

"""

field_example = """

Field names should be relatively short because they will be used
frequently (and need to fit in the register layout picture!) The field
description is expected to be longer and will most likely make use of
the Hjson ability to include multi-line strings. An example with three
fields:

```hjson
    fields: [
      { bits: "15:0",
        name: "RXS",
        desc: '''
        Last 16 oversampled values of RX. These are captured at 16x the baud
        rate clock. This is a shift register with the most recent bit in
        bit 0 and the oldest in bit 15. Only valid when ENRXS is set.
        '''
      }
      { bits: "16",
        name: "ENRXS",
        desc: '''
          If this bit is set the receive oversampled data is collected
          in the RXS field.
        '''
      }
      {bits: "20:19", name: "TXILVL",
       desc: "Trigger level for TX interrupts",
       resval: "2",
       enum: [
               { value: "0", name: "txlvl1", desc: "1 character" },
               { value: "1", name: "txlvl4", desc: "4 characters" },
               { value: "2", name: "txlvl8", desc: "8 characters" },
               { value: "3", name: "txlvl16", desc: "16 characters" }
             ]
      }
    ]
```

In all of these the swaccess parameter is inherited from the register
level, and will be added so this key is always available to the
backend. The RXS and ENRXS will default to zero reset value (unless
something different is provided for the register) and will have the
key added, but TXILVL expicitly sets its reset value as 2.

The missing bits 17 and 18 will be treated as reserved by the tool, as
will any bits between 21 and the maximum in the register.

The TXILVL is an example using an enumeration to specify all valid
values for the field. In this case all possible values are described,
if the list is incomplete then the field is marked with the rsvdenum
key so the backend can take appropriate action. (If the enum field is
more than 7 bits then the checking is not done.)

"""

offset_intro = """

"""

multi_intro = """

The multireg expands on the register required fields and will generate
a list of the generated registers (that contain all required and
generated keys for an actual register).

"""

window_intro = """

A window defines an open region of the register space that can be used
for things that are not registers (for example access to a buffer ram).

"""

regwen_intro = """

Registers can protect themselves from software writes by using the
register attribute regwen. When not an emptry string (the default
value), regwen indicates that another register must be true in order
to allow writes to this register.  This is useful for the prevention
of software modification.  The register-enable register (call it
REGWEN) must be one bit in width, and should default to 1 and be rw1c
for preferred security control.  This allows all writes to proceed
until at some point software disables future modifications by clearing
REGWEN. An error is reported if REGWEN does not exist, contains more
than one bit, is not `rw1c` or does not default to 1. One REGWEN can
protect multiple registers. The REGWEN register must precede those
registers that refer to it in the .hjson register list. An example:

```hjson
    { name: "REGWEN",
      desc: "Register write enable for a bank of registers",
      swaccess: "rw1c",
      fields: [ { bits: "0", resval: "1" } ]
    }
    { name: "REGA",
      swaccess: "rw",
      regwen: "REGWEN",
      ...
    }
    { name: "REGB",
      swaccess: "rw",
      regwen: "REGWEN",
      ...
    }
```
"""

doc_tail = """

(end of output generated by `regtool.py --doc`)

"""


def doc_tbl_head(outfile: TextIO, use: bool) -> None:
    if use:
        genout(outfile, "\nKey | Kind | Type | Description of Value\n")
        genout(outfile, "--- | ---- | ---- | --------------------\n")
    else:
        genout(outfile, "\nKey | Description\n")
        genout(outfile, "--- | -----------\n")


def doc_tbl_line(outfile: TextIO, key: str,
                 use: Optional[str], desc: Any) -> None:
    if use is not None:
        desc_key, desc_txt = desc
        val_type = (validate.val_types[desc_key][0]
                    if desc_key is not None else None)
    else:
        assert isinstance(desc, str)
        val_type = None
        desc_txt = desc

    if val_type is not None:
        assert use is not None
        genout(
            outfile, '{} | {} | {} | {}\n'.format(key, validate.key_use[use],
                                                  val_type, desc_txt))
    else:
        genout(outfile, key + " | " + desc_txt + "\n")


def document(outfile: TextIO) -> None:
    genout(outfile, doc_intro)
    for x in validate.val_types:
        genout(
            outfile,
            validate.val_types[x][0] + " | " + validate.val_types[x][1] + "\n")

    genout(outfile, swaccess_intro)
    doc_tbl_head(outfile, False)
    for key, value in SWACCESS_PERMITTED.items():
        doc_tbl_line(outfile, key, None, value[0])

    genout(outfile, hwaccess_intro)
    doc_tbl_head(outfile, False)
    for key, hw_value in HWACCESS_PERMITTED.items():
        doc_tbl_line(outfile, key, None, hw_value[0])

    genout(
        outfile, "\n\nThe top level of the JSON is a group containing "
        "the following keys:\n")
    doc_tbl_head(outfile, True)
    for k, v in ip_block.REQUIRED_FIELDS.items():
        doc_tbl_line(outfile, k, 'r', v)
    for k, v in ip_block.OPTIONAL_FIELDS.items():
        doc_tbl_line(outfile, k, 'o', v)
    genout(outfile, top_example)

    genout(
        outfile, "\n\nThe list of registers includes register definition "
        "groups containing the following keys:\n")
    doc_tbl_head(outfile, True)
    for k, v in register.REQUIRED_FIELDS.items():
        doc_tbl_line(outfile, k, 'r', v)
    for k, v in register.OPTIONAL_FIELDS.items():
        doc_tbl_line(outfile, k, 'o', v)
    genout(outfile, register_example)

    genout(
        outfile, "\n\nIn the fields list each field definition is a group "
        "itself containing the following keys:\n")
    doc_tbl_head(outfile, True)
    for k, v in field.REQUIRED_FIELDS.items():
        doc_tbl_line(outfile, k, 'r', v)
    for k, v in field.OPTIONAL_FIELDS.items():
        doc_tbl_line(outfile, k, 'o', v)
    genout(outfile, field_example)

    genout(outfile, "\n\nDefinitions in an enumeration group contain:\n")
    doc_tbl_head(outfile, True)
    for k, v in enum_entry.REQUIRED_FIELDS.items():
        doc_tbl_line(outfile, k, 'r', v)

    genout(
        outfile, "\n\nThe list of registers may include single entry groups "
        "to control the offset, open a window or generate registers:\n")
    doc_tbl_head(outfile, True)
    for x in validate.list_optone:
        doc_tbl_line(outfile, x, 'o', validate.list_optone[x])

    genout(outfile, offset_intro)
    genout(outfile, regwen_intro)

    genout(outfile, window_intro)
    doc_tbl_head(outfile, True)
    for k, v in window.REQUIRED_FIELDS.items():
        doc_tbl_line(outfile, k, 'r', v)
    for k, v in window.OPTIONAL_FIELDS.items():
        doc_tbl_line(outfile, k, 'o', v)

    genout(outfile, multi_intro)
    doc_tbl_head(outfile, True)
    for k, v in multi_register.REQUIRED_FIELDS.items():
        doc_tbl_line(outfile, k, 'r', v)
    for k, v in multi_register.OPTIONAL_FIELDS.items():
        doc_tbl_line(outfile, k, 'o', v)

    genout(outfile, doc_tail)
