#!/usr/bin/env python3
"""
MoatMoat-us Server Examples
--------------------------------------------------------------------------

The following is an example of how to use the async modbus server
implementation from moat.modbus.

"""
import asyncclick as click
import struct
import anyio
from getopt import getopt

from moat.modbus.server import ModbusServer
from moat.modbus.types import Coils,DiscreteInputs,HoldingRegisters,InputRegisters
from moat.modbus.types import IntValue,LongValue,SwappedLongValue,SignedIntValue,SignedLongValue,SwappedSignedLongValue,FloatValue,SwappedFloatValue,DoubleValue,SwappedDoubleValue,SignedQuadValue,QuadValue,SwappedQuadValue,SwappedSignedQuadValue
map_kind = {'c':Coils,'d':DiscreteInputs,'h':HoldingRegisters,'i':InputRegisters}
map_type = {
        'raw': IntValue,
        'u1': IntValue,
        'U1': IntValue,
        'u2': LongValue,
        'U2': SwappedLongValue,
        'u4': QuadValue,
        'U4': SwappedQuadValue,
        's1': SignedIntValue,
        'S1': SignedIntValue,
        's2': SignedLongValue,
        'S2': SwappedSignedLongValue,
        's4': SignedQuadValue,
        'S4': SwappedSignedQuadValue,
        'f2': FloatValue,
        'F2': SwappedFloatValue,
        'f4': DoubleValue,
        'F4': SwappedDoubleValue,
        }

import logging
FORMAT = ('%(asctime)-15s %(threadName)-15s '
          '%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.WARN)

UNIT = 0x1

def get_len(typ):
    if typ in {"r","raw"}:
        return 1
    return int(typ[1:])

def _doc(v):
    return v.__doc__.split("\n")[0]
_args_kind="\n".join(f"{k :3s}  {_doc(v)}" for k,v in map_kind.items())
_args_type="\n".join(f"{k :3s}  {_doc(v)}" for k,v in map_type.items())
_args_help=f"""\
\b
Setting values:
-u UNIT   register to this unit; default 1
-k KIND   use this register range, default Input
-t TYPE   register this datatype, default Raw
-r REGNUM start at this register, default 0, auto-incremented
-n COUNT  register this many variables, default 1
-v VALUE  set the register(s) to this value

\b
Kinds:
{_args_kind}

\b
Types:
{_args_type}
"""

@click.command(context_settings=dict(
        show_default=True,
        ignore_unknown_options=True,
        help_option_names=["-?","--help"],
    ), epilog=_args_help,
)
@click.option('--host','-s', default="localhost", help="host to bind to")
@click.option('--port','-p', type=int, default=502, help="port to bind to")
@click.option('--debug','-d', is_flag=True, help="Log debug messages")
@click.argument('args', nargs=-1, type=click.UNPROCESSED)
async def run(host, port, debug, args):
    if debug:
        log.setLevel(logging.DEBUG)
    if not args:
        click.UsageError("You didn't add any values to serve")

    s = ModbusServer(address=host, port=port)
    unit=None
    kind=InputRegisters
    typ=IntValue
    num=1
    reg=0

    kv,a = getopt(args, "u:k:t:r:n:v:",
            "--unit= --kind= --type= reg= register= num= val= value=".split())
    if a:
        raise click.UsageError(f"Unknown argument: {' ' .join(a)}")
    for k,v in kv:
        pend=True
        if k in ("-u","--unit"):
            unit = s.add_unit(int(v))
        elif k in ("-k", "--kind"):
            kind = map_kind[v[0]]
        elif k in ("-t", "--type"):
            typ = map_type[v[0]]
        elif k in ("-r", "--reg", "--register"):
            reg = int(v)
        elif k in ("-n", "--num"):
            num=int(v)
        elif k in ("-v", "--val", "--value"):
            try:
                v=int(v)
            except ValueError:
                v=float(v)
            if unit is None:
                unit = s.add_unit(int(1))
            for _ in range(num):
                unit.add(kind,reg,typ(v))
                reg += typ.len
            pend=False
        else:
            raise click.UsageError(f"Unknown argument: {k !r}")
    if pend:
        raise click.UsageError(f"Values must be at the end")

    await s.serve()


if __name__ == "__main__":
    run(_anyio_backend="trio")
