import re


def _run_all(self, numprocs, fstype, tests, testtype):
    testdir = self.mkworkdir('d')

    share = self.share_command(fstype)
    name = 'shardlrn'

    tstring=''
    for t in tests:
        tstring += ("c:\\%s %s %s %s %s %s\n" %
                    (name, 'single' if numprocs == "ONE" else 'primary', *t, testtype))

    self.mkfile("testit.bat", f"""\
set LFN=n
d:
{share}
{tstring}
rem tests complete
rem end
""", newline="\r\n")

    # compile sources
    self.mkexe_with_djgpp(name, r"""
#include <dos.h>
#include <dir.h>
#include <fcntl.h>
#include <io.h>
#include <process.h>
#include <share.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

/* must be 8.3 exactly for FCB tests below */
#define FN1 "FOO34567"
#define FE1 "OLD"
#define FN2 "BAR12345"
#define FE2 "NEW"

unsigned short sharemode(const char *s) {
  if (strcmp(s, "SH_COMPAT") == 0)
    return SH_COMPAT;

  if (strcmp(s, "SH_DENYRW") == 0)
    return SH_DENYRW;

  if (strcmp(s, "SH_DENYWR") == 0)
    return SH_DENYWR;

  if (strcmp(s, "SH_DENYRD") == 0)
    return SH_DENYRD;

  if (strcmp(s, "SH_DENYNO") == 0)
    return SH_DENYNO;

  return 0xff;
}

unsigned short openmode(const char *s) {
  if (strcmp(s, "RW") == 0)
    return O_RDWR;
  if (s[0] == 'R')
    return O_RDONLY;
  if (s[0] == 'W')
    return O_WRONLY;
  return 0xff;
}

int main(int argc, char *argv[]) {
  int handle;
  int ret;
  enum {
    PRIMARY,
    SECONDARY,
    SINGLE,
  } ptype;

  unsigned short smode, omode;
  enum {
    DELFCB,
    RENFCB,
    DELPTH,
    RENPTH,
    SETATT,
  } testmode;

  if (argc < 6) {
    printf("FAIL: Missing arguments (primary|secondary|single) sharemode openmode expected (DELFCB|RENFCB|DELPTH|RENPTH|SETATT)\n");
    return -2;
  }

  if (strcmp(argv[1], "primary") == 0)
    ptype = PRIMARY;
  else if (strcmp(argv[1], "secondary") == 0)
    ptype = SECONDARY;
  else if (strcmp(argv[1], "single") == 0)
    ptype = SINGLE;
  else {
    printf("FAIL: Invalid argument (primary|secondary|single)\n");
    return -2;
  }

  smode = sharemode(argv[2]);
  if (smode == 0xff) {
    printf("FAIL: Invalid argument sharemode '%s'\n", argv[2]);
    return -2;
  }
  omode = openmode(argv[3]);
  if (omode == 0xff) {
    printf("FAIL: Invalid argument openmode '%s'\n", argv[3]);
    return -2;
  }

  // expected result is argv[4]

  if (strcmp(argv[5], "DELFCB") == 0)
    testmode = DELFCB;
  else if (strcmp(argv[5], "RENFCB") == 0)
    testmode = RENFCB;
  else if (strcmp(argv[5], "DELPTH") == 0)
    testmode = DELPTH;
  else if (strcmp(argv[5], "RENPTH") == 0)
    testmode = RENPTH;
  else if (strcmp(argv[5], "SETATT") == 0)
    testmode = SETATT;
  else {
    printf("FAIL: Invalid argument testmode '%s'\n", argv[5]);
    return -2;
  }

  // Print results in consistent format
  // FAIL:("SH_DENYNO", "RW", "ALLOW")[secondary denied]

  if (ptype == PRIMARY || ptype == SINGLE) {
    unsigned short mode = smode | omode;

    // remove the source file so we can create it anew
    unlink(FN1 "." FE1);

    // create the file
    ret = _dos_creatnew(FN1 "." FE1, _A_NORMAL, &handle);
    if (ret != 0) {
      printf("FAIL:('%s', '%s', '%s')[primary create]\n", argv[2], argv[3], argv[4]);
      return -1;
    }
    _dos_close(handle);

    // remove the target file for rename
    unlink(FN2 "." FE2);

    // open with correct flags
    ret = _dos_open(FN1 "." FE1, mode, &handle);
    if (ret != 0) {
      printf("FAIL:('%s', '%s', '%s')[primary denied]\n", argv[2], argv[3], argv[4]);
      return -1;
    }
//    printf("INFO: primary: File was opened with mode 0x%04x\n", mode);

  }

  if (ptype == PRIMARY) {

    // Now start second copy
    spawnlp(P_WAIT, argv[0], argv[0], "secondary", argv[2], argv[3], argv[4], argv[5], NULL);

    _dos_close(handle);
    return 0;
  }

  if (ptype == SECONDARY || ptype == SINGLE) {

    switch(testmode) {

      case DELFCB: {
        /*
          DOS 1+ - DELETE FILE USING FCB

          AH = 13h
          DS:DX -> unopened FCB (see #01345), filename filled with template for
          deletion ('?' wildcards allowed)

          Return:
            AL = status
              00h one or more files successfully deleted
              FFh no matching files or all were read-only or locked
        */

        struct {
          uint8_t drive;
          char name[8];
          char ext[3];
          char padding[25];
        } fcb;
        uint16_t ax;

        fcb.drive = 0;
        memcpy(&fcb.name, FN1, 8);
        memcpy(&fcb.ext, FE1, 3);

        asm volatile("int $0x21\n"
                     : "=a"(ax)
                     : "a"(0x1305), "d"(&fcb)
                     : /* nothing clobbered */ );
        ret = ax & 0xff;
        break;
      }

      case RENFCB: {
        /*
          DOS 1+ - RENAME FILE USING FCB

            AH = 17h
            DS:DX -> modified FCB (see also #01345)
                    the old filename ('?' wildcards OK) is in the standard location
                    while the new filename ('?' wildcards OK, no drive) is stored
                    in the 11 bytes beginning at offset 11h

          Return:
            AL = status
              00h successfully renamed
              FFh no matching files,file is read-only, or new name already exists
        */

        struct {
          uint8_t drive;
          char o_name[8];
          char o_ext[3];
          char pad1[5];
          char n_name[8];
          char n_ext[3];
          char pad2[16];
        } xfcb;

        uint16_t ax;

        xfcb.drive = 0;
        memcpy(&xfcb.o_name, FN1, 8);
        memcpy(&xfcb.o_ext, FE1, 3);
        memcpy(&xfcb.n_name, FN2, 8);
        memcpy(&xfcb.n_ext, FE2, 3);

        asm volatile("int $0x21\n"
                     : "=a"(ax)
                     : "a"(0x1705), "d"(&xfcb)
                     : /* nothing clobbered */ );
        ret = ax & 0xff;
        break;
      }

      case DELPTH: {
        /*
          DOS 2+ - UNLINK - DELETE FILE

          AH = 41h
          DS:DX -> ASCIZ filename (no wildcards, but see notes)
          CL = attribute mask for deletion (server call only, see notes)

          Return:
            CF clear if successful
              AX destroyed (DOS 3.3) AL seems to be drive of deleted file
            CF set on error
              AX = error code (02h,03h,05h) (see #01680 at AH=59h/BX=0000h)
        */

        char *fname = FN1 "." FE1;
        uint8_t carry;

        asm volatile("int $0x21\n"
                     "setc %0\n"
                     : "=r"(carry)
                     : "a"(0x4100), "d"(fname), "c"(0x0)
                     : "cc", "memory");
        ret = carry ? -1 : 0;
        break;
      }

      case RENPTH: {
        /*
          DOS 2+ - RENAME - RENAME FILE

          AH = 56h
          DS:DX -> ASCIZ filename of existing file (no wildcards, but see below)
          ES:DI -> ASCIZ new filename (no wildcards)
          CL = attribute mask (server call only, see below)

          Return:
            CF clear if successful
            CF set on error
              AX = error code (02h,03h,05h,11h) (see #01680)
        */

        char *fn1 = FN1 "." FE1;
        char *fn2 = FN2 "." FE2;
        uint8_t carry;

        asm volatile("int $0x21\n"
                     "setc %0\n"
                     : "=r"(carry)
                     : "a"(0x5600), "d"(fn1), "D"(fn2), "c"(0x0)
                     : "cc", "memory");
        ret = carry ? -1 : 0;
        break;
      }

      case SETATT: {
        /*
          DOS 2+ - CHMOD - SET FILE ATTRIBUTES
        */

        const char *fname = FN1 "." FE1;
        unsigned int oattr, nattr;
        int rc;

        rc = _dos_getfileattr(fname, &oattr);
        if (rc != 0) {
          printf("FAIL:('%s', '%s', '%s')[secondary getattr1 failed(%d)]\n",
            argv[2], argv[3], argv[4], rc);
          return -1;
        }
        if (oattr & _A_HIDDEN) {
          printf("FAIL:('%s', '%s', '%s')[secondary getattr1 invalid(0x%02x)]\n",
            argv[2], argv[3], argv[4], oattr);
          return -1;
        }

        rc = _dos_setfileattr(fname, oattr | _A_HIDDEN);
        if (rc != 0) {
          printf("INFO:('%s', '%s', '%s')[secondary setattr failed(%d)]\n",
            argv[2], argv[3], argv[4], rc);
        } else {
          int rc2;

          printf("INFO:('%s', '%s', '%s')[secondary setattr success]\n",
            argv[2], argv[3], argv[4]);

          rc2 = _dos_getfileattr(fname, &nattr);
          if (rc2 != 0) {
            printf("INFO:('%s', '%s', '%s')[secondary getattr2 failed(%d)]\n",
              argv[2], argv[3], argv[4], rc2);
          } else if (nattr != (oattr | _A_HIDDEN)) {
            printf("INFO:('%s', '%s', '%s')[secondary getattr2 invalid(0x%02x)]\n",
              argv[2], argv[3], argv[4], nattr);
          }
        }

        ret = (rc != 0) ? -1 : 0;
        break;
      }

    } // switch

    if (ret != 0) {
      if (strcmp(argv[4], "DENY") == 0) {
        printf("PASS:('%s', '%s', '%s')[secondary denied]\n",
            argv[2], argv[3], argv[4]);
      } else {
        printf("FAIL:('%s', '%s', '%s')[secondary denied]\n",
            argv[2], argv[3], argv[4]);
      }
      return -1;
    }
    if (strcmp(argv[4], "ALLOW") == 0) {
      printf("PASS:('%s', '%s', '%s')[secondary allowed]\n",
          argv[2], argv[3], argv[4]);
    } else {
      printf("FAIL:('%s', '%s', '%s')[secondary allowed]\n",
          argv[2], argv[3], argv[4]);
    }
  }

  if (ptype == SINGLE) {
    _dos_close(handle);
  }

  return 0;
}
""")

    config = """$_floppy_a = ""\n"""
    if fstype == "MFS":
        config += """$_hdimage = "dXXXXs/c:hdtype1 dXXXXs/d:hdtype1 +1"\n"""
    else:       # FAT
        name = self.mkimage("12", cwd=testdir)
        config += """$_hdimage = "dXXXXs/c:hdtype1 %s +1"\n""" % name

    return self.runDosemu("testit.bat", config=config, timeout=60)

TESTS_ONE_PROCESS = (
    ("SH_COMPAT", "R" , "ALLOW"),
    ("SH_COMPAT", "W" , "ALLOW"),
    ("SH_COMPAT", "RW", "ALLOW"),

    ("SH_DENYRW", "R" , "DENY"),
    ("SH_DENYRW", "W" , "DENY"),
    ("SH_DENYRW", "RW", "DENY"),

    ("SH_DENYWR", "R" , "DENY"),
    ("SH_DENYWR", "W" , "DENY"),
    ("SH_DENYWR", "RW", "DENY"),

    ("SH_DENYRD", "R" , "DENY"),
    ("SH_DENYRD", "W" , "DENY"),
    ("SH_DENYRD", "RW", "DENY"),

    ("SH_DENYNO", "R" , "DENY"),
    ("SH_DENYNO", "W" , "DENY"),
    ("SH_DENYNO", "RW", "DENY"),
)

TESTS_TWO_PROCESS_FAT = (
    ("SH_COMPAT", "R" , "DENY"),
    ("SH_COMPAT", "W" , "DENY"),
    ("SH_COMPAT", "RW", "DENY"),

    ("SH_DENYRW", "R" , "DENY"),
    ("SH_DENYRW", "W" , "DENY"),
    ("SH_DENYRW", "RW", "DENY"),

    ("SH_DENYWR", "R" , "DENY"),
    ("SH_DENYWR", "W" , "DENY"),
    ("SH_DENYWR", "RW", "DENY"),

    ("SH_DENYRD", "R" , "DENY"),
    ("SH_DENYRD", "W" , "DENY"),
    ("SH_DENYRD", "RW", "DENY"),

    ("SH_DENYNO", "R" , "DENY"),
    ("SH_DENYNO", "W" , "DENY"),
    ("SH_DENYNO", "RW", "DENY"),
)

# SH_COMPAT makes no sense on network drive, see
# https://github.com/dosemu2/dosemu2/pull/1623#issuecomment-1108776612
TESTS_TWO_PROCESS_MFS = tuple(TESTS_TWO_PROCESS_FAT)[3:]


def _check_single_result(self, results, t):
    m = re.search(r"FAIL:\('%s', '%s', '%s'\)\[.*\]" % t, results)
    if m:
        self.fail(msg=m.group(0))

def ds3_share_open_access(self, numprocs, fstype, testtype):
    if numprocs == "ONE":
        tests = TESTS_ONE_PROCESS
    else:         # TWO
        if fstype == "MFS":
            tests = TESTS_TWO_PROCESS_MFS
        else:       # FAT
            tests = TESTS_TWO_PROCESS_FAT

    results = _run_all(self, numprocs, fstype, tests, testtype)
    for t in tests:
        _check_single_result(self, results, t)
    self.assertIn("rem tests complete", results)

