/*
 * WWVB BPSK CODE
 * https://www.xtcn.com/wwvb/
 * mailto:wwvb@xtcn.com
 *
 * Copyright notice:
 * Parts not enclosed between *copyright*...*end copyright*, are copyright:
 * SPDX-FileCopyrightText: Copyright (c) 2025-2026 Richard Lamb
 * SPDX-License-Identifier: CC BY-NC-SA-4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
 * Author: RHLamb (W3TOY) wwvb@xtcn.com (Human)
 * Hey! Even poor developers deserve something for our work.
 * Contact for reasonable commercial licensing terms.
 *
 * The above copyright notice shall be included in all copies or substantial portions of this software.
 *
 */
#define THISVERSION "V1.001"
#define FINDABLEVERSION THISVERSION" UXKCLK Ricks dc.org (c) Dept C Inc 2025-2026" // DONOTEDIT used to find version

/*
 * changelog
 * v1.000 - Release
 * v1.001 - New tinyusb changed api and drops many bytes. Replaced with older working version.
 *          See main/idf_component.yml for updated deplendency.
 *          Also fixed fwupdate routines in console mode to work with old tinyusb.
 *          Misc reset reason reporting in "stats" command.
 */
#include <stdio.h>
#include <time.h>
#include <stdatomic.h>
#include "esp_err.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
#include "driver/ledc.h"
#include "driver/gpio.h"

#ifdef CONFIG_IDF_TARGET_ESP32S2
#define DO_GPS
#define DO_VCXO_FREQ  // if Pin38(esp32s3) 11(esp32s2) connected to VCXO and not GPS
/*
gpio map: esp32s2 driving 3.5in 480x320 SPI Serial ILI9488/XPT2046
scl:2 sda:1 to si5351 aic3254
bclk:6 ws:5 dout:3 din:4 to aic3254
pwm-vcxo:11  to vcxo rc filter
backlight:36 to lcd-8 (led)
mosi:34 to lcd-6,12 (sdi,t_din)
miso:33 to lcd-13 (t_do)
spiclk:37 to lcd-7,10 (sck,t_clk)
spics:40 to lcd-3 (cs)
spidc:38 to lcd-5 (dc/rs)
spirst:39 to lcd-4 (reset)
spitouch:35 to lcd-11 (t_cs)
gps:10 to gps module output
 */
#endif
#ifdef CONFIG_IDF_TARGET_ESP32S3
#define DO_GPS
#define DO_WIFI // beware: wifi will mess up LCD display. this is just a MPU after all.
/*
gpio map: CrowPanel 7inch -HMI ESP32S3 Display 800x480 RGB TFT LCD Touch Screen DIS08070H 
scl:20 sda:19 to si5351 aic3254
bclk:42 ws:18 dout:17 din:n/a to aic3254 Note:remove 0ohm R's on display board
pwm-vcxo:n/a
gps:38 to gps module output
sdcard cs:10 
sdcard mosi:11
sdcard miso:13
sdcard sclk:12
lcd hsync:39
lcd vsync:40
lcd de:41
lcd pclk:0
lcd d0-15:14,21,47,48,45,9,46,3,8,16,1,15,7,6,5,4
nc??:19 20 22-37 43-44
 */
#endif

#define min(x,y) (((x)<(y))?(x):(y))
#define max(x,y) (((x)>(y))?(x):(y))

#include "lvgl.h"

#pragma GCC diagnostic ignored "-Wmissing-braces"
//#pragma GCC diagnostic ignored "-Wunused-variable"
//#pragma GCC diagnostic ignored "-Wunused-function"

/*
 * my common support libs
 *
 * Copyright notice:
 * SPDX-FileCopyrightText: Copyright (c) 2025-2026 Richard Lamb
 * SPDX-License-Identifier: CC BY-NC-SA-4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
 * Author: RHLamb (W3TOY) wwvb@xtcn.com
 *
 * The above copyright notice shall be included in all copies or substantial portions of this software.
 */
int debug=2;
// usually run my own mem
#define mymalloc malloc
#define myfree free
#define mystrdup strdup
#define myrealloc realloc
static uint32_t memmax=0,memtot=0;
static uint8_t myconsole_logok=1;
static uint8_t consoleok=1; // console_proc()
void rlprintf(const char *fnc,const char *format, ...);
#define myprintf(...) rlprintf(NULL,__VA_ARGS__)
#define myDprintf(...) rlprintf(__func__,__VA_ARGS__)
#define myNprintf myprintf  // a fast non-blocking printf
//#define myNprintf printf // lazy fast non-blocking printf
static SemaphoreHandle_t rlprintfmux;
#define RLPRINTFMUXTMO 0 // 0=just poll flag was=2 ticks
#define memshred(a,b) memset(a,0,b)
#define mytime time
//static int64_t mystarttime=0; // could just use esp_timer_get_time()
RTC_DATA_ATTR time_t mystarttime; 
static char *ssid=NULL,*wifipass=NULL;
static uint32_t haveinet=0;
static char *ntpserver=NULL;
static uint8_t mymac[6];
static int disable_spi_ops=0; // disable when doing FLASH/OTA fwupdate
static time_t curtime0=0; // -0 gives us idea of what century we are in
static atomic_int_fast64_t curtime;
static double cur_lo_freq=239600.00; // /4 in tayloe mixer. note:100hz bpf may be +/-10hz
static double cur_fs_freq=819200.00; // 256*3.2kc adc 3.2ksamp/sec

static char *dodiskstuff=NULL;
static char config_tz[80];
static int config_mode=0;
static int rotatedisp=0;

#include "esp_task_wdt.h"
static void my_watchdog_refresh(void)
{
  esp_task_wdt_reset();
}
static void mysetupwtchdog(int secs)
{
  esp_task_wdt_config_t config = {
    .timeout_ms = secs*1000,
    .idle_core_mask = (1 << portNUM_PROCESSORS) - 1,
    .trigger_panic = false
  };
  if(esp_task_wdt_status(NULL) == ESP_ERR_INVALID_STATE) {
    ESP_ERROR_CHECK(esp_task_wdt_init(&config));
  } else {
    ESP_ERROR_CHECK(esp_task_wdt_reconfigure(&config));
  }
  ESP_ERROR_CHECK(esp_task_wdt_add(NULL));
}
static void mywatchdogyield(void)
{
  my_watchdog_refresh();
  //taskYIELD(); // using this triggers IDLE0 watchdog
  vTaskDelay(1);
}
static void myreset(void) { esp_restart(); }
static void mydelay(uint32_t t)
{
  vTaskDelay(pdMS_TO_TICKS(t)); // t/portTICK_PERIOD_MS (t*configTICK_RATE_HZ)/1000
}
static void mysleep(int sec)
{
  for(int i=0;i<sec;i++) {
    my_watchdog_refresh();
    vTaskDelay( pdMS_TO_TICKS(1000) ); // (1000/portTICK_PERIOD_MS) );
  }
}
static int rdump(void *vp,int n)
{
  uint8_t *ptr=(uint8_t *)vp;
  int i,j1,j2; char buf[80]; static char htoas[]="0123456789ABCDEF";
  j1 = j2 = 0; /* gcc -W */
  for(i=0;i<n;i++,j1+=3,j2++) {
    if((i&0xf) == 0) {
      if(i) { buf[j2]='\0'; myprintf("%s|\r\n",buf); }
      j1=0; j2=51; memset(buf,' ',80); buf[50]='|';
    }
    buf[j1] = htoas[(ptr[i]&0xf0) >> 4]; buf[j1+1] = htoas[ptr[i]&0x0f];
    if(ptr[i] >= 0x20 && ptr[i] < 0x80) buf[j2]=ptr[i]; else buf[j2]='.';
  }
  buf[j2]='\0'; myprintf("%s|\r\n",buf);
  return 0;
}
static int lparse(char *line,char *argv[],int maxargs,char delc)
{
  char *cp;
  int argc,qflag;

  if((cp = strchr(line,'\r')) != (char *)0) *cp = '\0';
  if((cp = strchr(line,'\n')) != (char *)0) *cp = '\0';
  for(argc=0;argc<maxargs;argc++) argv[argc] = (char *)0;
  for(argc=0;argc<maxargs;) {
    qflag = 0;
    while(*line == ' ' || *line == '\t') line++;
    if(*line == '\0') break;
    if(*line == '"') { line++; qflag = 1; }
    argv[argc++] = line;
    if(qflag) {
      if((line=strchr(line,'"')) == NULL) return -1;
      *line++ = '\0';
    }
    for(cp=line;*cp;cp++) if(/* *cp == ' ' || *cp == '\t' ||*/ *cp == delc) break;
    if(*cp) *cp++ = '\0';
    line = cp;
  }
  return argc;
}
static int hex2i(char c)
{
  if(c >= '0' && c <= '9') return (int)(c - '0');
  if(c >= 'A' && c <= 'F') return (int)((c - 'A') + 10);
  if(c >= 'a' && c <= 'f') return (int)((c - 'a') + 10);
  return -1;
}
static int ahex2bin(uint8_t *out,int olen,char *in)
{
  uint8_t *p;
  int i,j;
  p = out;
  while(*in) {
    i = hex2i(in[0]);
    j = hex2i(in[1]);
    if(i < 0 || j < 0) return -1;
    in += 2;
    *p++ = (uint8_t) ((i<<4)|j); // make sure inplace ahex 2 bin works
    if(--olen < 0) {
      myDprintf("out buffer not large enough\n");
      break;
    }
  }
  return (int)(p - out);
}
static char *pipaddr(uint32_t ip)
{
  static char out[20];
  uint8_t *p;
  p = (uint8_t *)&ip;
  sprintf(out,"%d.%d.%d.%d",p[0],p[1],p[2],p[3]);
  return out;
}
/*end copyright*/
/*
 * my i2c stuff
 */
#include "driver/i2c_master.h"
#ifdef CONFIG_IDF_TARGET_ESP32S2
#define MYGPIO_SCL_PIN 2
#define MYGPIO_SDA_PIN 1
#endif
#ifdef CONFIG_IDF_TARGET_ESP32S3
#define MYGPIO_SCL_PIN 20
#define MYGPIO_SDA_PIN 19
#endif

#define MYGPIO_I2C_RST GPIO_NUM_NC
#define I2C_CLK_SPEED_HZ 400000
static i2c_master_bus_handle_t i2c_handle;
static int myi2cinit(void)
{
  static int myi2cinited=0;
  if(myi2cinited) return 0;
  i2c_master_bus_config_t i2c_bus_config = {
    .clk_source = I2C_CLK_SRC_DEFAULT,
    .i2c_port = I2C_NUM_0,
    .scl_io_num = MYGPIO_SCL_PIN,
    .sda_io_num = MYGPIO_SDA_PIN,
    
    .glitch_ignore_cnt = 7,
    .flags.enable_internal_pullup = true,
    
    //.flags.allow_pd = false,
    //.trans_queue_depth = 0,
  };
  ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config,&i2c_handle));
  mydelay(100);
  myi2cinited = 1;
  myDprintf("initialized\n");
  return 0;
}
static int myi2cscan(int argc,char *argv[])
{
  uint8_t addr;
  if(myi2cinit()) return -1;
  myprintf("     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f\n");
  for(int i=0;i<128;i+=16) {
    myprintf("%02x: ",i);
    for(int j=0;j<16;j++) {
      addr = i + j;
      esp_err_t ret=i2c_master_probe(i2c_handle,addr,50); // 50ms timeout
      if(ret == ESP_OK) {
	myprintf("%02x ",addr);
      } else if(ret == ESP_ERR_TIMEOUT) {
	myprintf("UU ");
      } else {
	myprintf("-- ");
      }
    }
    myprintf("\n");
  }
  return 0;
}
static int myi2ccheck(uint8_t addr)
{
  if(myi2cinit()) return -1;
  i2c_master_probe(i2c_handle,addr,50);
  mysleep(1); // the 0.42 inch ones seems to need this     
  i2c_master_probe(i2c_handle,addr,50);
  if(i2c_master_probe(i2c_handle,addr,50) == ESP_OK) return 1;
  return 0;
}
static int myi2cinitdev(uint8_t addr,int speed,i2c_master_dev_handle_t *myi2cdev)
{
  if(speed == 0) speed = I2C_CLK_SPEED_HZ; // 100000; // 100kc
  i2c_device_config_t dev_config = {
    .dev_addr_length = I2C_ADDR_BIT_LEN_7,
    .device_address = addr, 
    .scl_speed_hz = speed,
  };
  if(i2c_master_bus_add_device(i2c_handle,&dev_config,myi2cdev)) return -1;
  return 0;
}
static int _myi2cwrite(i2c_master_dev_handle_t myi2cdev,uint8_t *p,int n,const char *f)
{
  int tmo = 1000/portTICK_PERIOD_MS;
  if(i2c_master_transmit(myi2cdev,p,n,tmo)) {
    myDprintf("%s: failed\n",f);
    return 0;
  }
  return n;
}
#define myi2cwrite(myi2cdev,p,n) _myi2cwrite(myi2cdev,p,n,__func__)



/*
 * FAT filesystem stuff
 */
#ifdef CONFIG_IDF_TARGET_ESP32S2
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_vfs_fat.h"
// use on-chip flash partition named "storage"
#define MYMOUNTPT "/config"
static int myfatinited=0;
static int fatinit(void)
{
  if(myfatinited) return 0;
  const esp_vfs_fat_mount_config_t mount_config = {
    .max_files = 5,
    .format_if_mount_failed = true,
    .allocation_unit_size = 16 * 1024,
    .use_one_fat = false,
  };
  wl_handle_t wl_handle = WL_INVALID_HANDLE;
  if(esp_vfs_fat_spiflash_mount_rw_wl(MYMOUNTPT,"storage",&mount_config,&wl_handle)) {
    myprintf("Failed to mount filesystem.\n");
    return -1;
  }
  myfatinited = 1;
  return 0;
}
static int doumount(int argc,char *argv[])
{
  return 0;
}
static int domount(int argc,char *argv[])
{
  return fatinit();
}
static int doformat(int argc,char *argv[])
{
  return 0;
}
#endif // CONFIG_IDF_TARGET_ESP32S2
#ifdef CONFIG_IDF_TARGET_ESP32S3
/* SD card and FAT filesystem example.
 *copyright*
   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
// cs:10 mosi:11 sclk:12 miso:13 for cropanel 7inch v3.0 20240420
#define MYMOUNTPT "/sdcard"
#define MYSDSPI SPI3_HOST
static sdmmc_card_t *mysdcard=NULL;
static int fatinit(void)
{
  if(mysdcard) return 0;
  esp_err_t ret;
  esp_vfs_fat_sdmmc_mount_config_t mount_config = {
    .format_if_mount_failed = false,
    .max_files = 5,
    .allocation_unit_size = 16 * 1024
  };
  const char mount_point[] = MYMOUNTPT;
  sdmmc_host_t host = SDSPI_HOST_DEFAULT();

  host.slot = MYSDSPI;
  spi_bus_config_t bus_cfg = {
    .mosi_io_num = 11,
    .miso_io_num = 13,
    .sclk_io_num = 12,
    .quadwp_io_num = -1,
    .quadhd_io_num = -1,
    .max_transfer_sz = 4000,
  };
  // SDSPI_DEFAULT_DMA
  if(spi_bus_initialize(host.slot,&bus_cfg,SPI_DMA_CH_AUTO) != ESP_OK) {
    printf("Failed to initialize bus.\n");
    return -1;
  }
  
  sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
  slot_config.gpio_cs = 10;
  slot_config.host_id = host.slot;
  ret = esp_vfs_fat_sdspi_mount(mount_point,&host,&slot_config,&mount_config,&mysdcard);
  
  if(ret != ESP_OK) {
    if(ret == ESP_FAIL) {
      printf("Failed to mount filesystem. "
               "If you want the card to be formatted, set the CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.\n");
    } else {
      printf("Failed to initialize the card (%s). "
               "Make sure SD card lines have pull-up resistors in place.\n",esp_err_to_name(ret));
    }
    mysdcard = NULL;
    return -1;
  }
  //sdmmc_card_print_info(stdout,mysdcard);  
  return 0;
}
/*end copyright*/

static int doumount(int argc,char *argv[])
{
  // loosing 16 bytes per mount/dismount/mount
  if(mysdcard) {
    const char mount_point[] = MYMOUNTPT;
    esp_vfs_fat_sdcard_unmount(mount_point,mysdcard);
    spi_bus_free(MYSDSPI);
  }
  mysdcard = NULL;
  return 0;
}
static int domount(int argc,char *argv[])
{
  if(mysdcard) { // unmount whatever was there
    const char mount_point[] = MYMOUNTPT;
    esp_vfs_fat_sdcard_unmount(mount_point,mysdcard);
    spi_bus_free(MYSDSPI);
  }
  mysdcard = NULL;
  return fatinit();
}
static int doformat(int argc,char *argv[])
{
  if(fatinit()) return -1;
  esp_err_t ret;
  if((ret=esp_vfs_fat_sdcard_format(MYMOUNTPT,mysdcard)) != ESP_OK) {
    myprintf("Failed to format FATFS (%s)\n",esp_err_to_name(ret));
    return -1;
  }
  return 0;
}
#endif // CONFIG_IDF_TARGET_ESP32S3

static int dodel(int argc,char *argv[])
{
  if(fatinit()) return -1;
  char fname[264];
  snprintf(fname,sizeof(fname),"%s/%s",MYMOUNTPT,argv[1]);
  unlink(fname);
  return 0;
}
static int dofdump(int argc,char *argv[])
{
  if(fatinit()) return -1;
  char fname[264];
  FILE *fp;
  int n;
  uint8_t buf[64];
  snprintf(fname,sizeof(fname),"%s/%s",MYMOUNTPT,argv[1]);
  if((fp=fopen(fname,"rb")) == NULL) {
    myprintf("Could not read %s\n",fname);
    return -1;
  }
  while((n=fread(buf,1,sizeof(buf),fp)) > 0) {
    rdump(buf,n);
  }
  fclose(fp);
  return 0;
}
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
static int dodir(int argc,char *argv[])
{  
  if(fatinit()) return -1;
  char fname[264];
  DIR *dirp;
  struct dirent *dp;
  char *scandir=MYMOUNTPT;  
  if((dirp=opendir(scandir)) == (DIR *)0) {
    myprintf("Cannot open %s directory\n",scandir);
    return -1;
  }
  struct stat st;
  while((dp=readdir(dirp))) {
    if(strcmp(dp->d_name,".") == 0 || strcmp(dp->d_name,"..") == 0) continue;
    snprintf(fname,sizeof(fname),"%s/%s",scandir,dp->d_name);
    stat(fname,&st);
    myprintf("%s %d\n",fname,(int)st.st_size);
  }
  closedir(dirp);
  
  uint64_t outb,outf;
  esp_vfs_fat_info(MYMOUNTPT,&outb,&outf);
  myprintf("size=%.2fM used=%.2fM bytes\n",
	   ((float)outb)/1000000,((float)(outb-outf))/1000000);

  return 0;
}

/*copyright*/
//#define FWUTEST  // define for test w/o actual update
/**********************************************************************
 * my fwupdate and ota firmware update routines
 *
 * Based on Espressif code samples:
 * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
 * SPDX-License-Identifier: CC0-1.0
 * Mods/additions:
 * SPDX-FileCopyrightText: Copyright (c) 2025-2026 Richard Lamb
 * SPDX-License-Identifier: CC BY-NC-SA-4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
 * Author: RHLamb (W3TOY) wwvb@xtcn.com
 *
 * The above copyright notice shall be included in all copies or substantial portions of this software.
 *
 **********************************************************************/
#ifdef CONFIG_IDF_TARGET_ESP32S2
#define thisfwupdateurl "http://www.xtcn.com/wwvb/wwvbbpsks2.binx"
#endif // CONFIG_IDF_TARGET_ESP32S2
#ifdef CONFIG_IDF_TARGET_ESP32S3
#define thisfwupdateurl "http://www.xtcn.com/wwvb/wwvbbpsks3.binx"
#endif // CONFIG_IDF_TARGET_ESP32S3
#define thisfwupdatepubkey "2bc65982eabcfcd1c8e4264343053f728df305be176987195d9e33d81f632cb5"

#include "esp_ota_ops.h"
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
#define FLASH_SECTOR_SIZE 0x1000  // 4096
static uint8_t curimg=0; // running which image? OTA1 or OTA2
static char *getcurimg(void)
{
  static char out[80];
  const esp_partition_t *run = esp_ota_get_running_partition();
  if(run == NULL) {
    sprintf(out,"Could not get running partition info.");
    return out;
  }
  sprintf(out,"Running part: addr=0x%08lx type=%d subtype=%d",run->address,run->type,run->subtype);
  return out;
}
typedef struct {
  char *fwupdateurl;
  char *fwupdatepubkey;
} myparamcb;
static myparamcb _myparams={thisfwupdateurl,thisfwupdatepubkey};
static myparamcb *myparams=&_myparams;

#define RECV_TO        10000 // was 5000ms
int lcled25519_verify(const uint8_t *sig,const uint8_t *msg,size_t len,const uint8_t *pub);
static void app_end(void);
static ssize_t (*fwupread)(int fd,void *buf,size_t count);
static ssize_t (*fwupwrite)(int fd,const void *buf,size_t count);
static int (*fwupclose)(int fd);
static ssize_t consoleread(int fd,void *buf,size_t count);
static ssize_t consolewrite(int fd,const void *buf,size_t count);
static int consoleclose(int fd);
static FILE *myfwfp=NULL;
static ssize_t diskfwread(int fd,void *buf,size_t count)
{
  vTaskDelay(10); // must yield for myprintf output
  return fread(buf,1,count,myfwfp);
}
static ssize_t diskfwwrite(int fd,const void *buf,size_t count)
{
  return 0;
}
static int diskfwclose(int fd)
{
  int ret=fclose(myfwfp);
  myfwfp = NULL;
  return ret;
}
static int fwupdate(int fwupdateio)
{
  if(fwupdateio == 1) { // console
    fwupread = &consoleread;
    fwupwrite = &consolewrite;
    fwupclose = &consoleclose;
   } else if(fwupdateio == 0) { // Net
    fwupread = &read;
    fwupwrite = &write;
    fwupclose = &close;    
  } else if(fwupdateio == 2) { // disk
    fwupread = &diskfwread; // fread()
    fwupwrite = &diskfwwrite; // NOP
    fwupclose = &diskfwclose; // fclose
  }
  
  int i,j;
  esp_err_t err;
  esp_ota_handle_t update_handle=0; // set by esp_ota_begin(), must be freed via esp_ota_end()/_abort()
  const esp_partition_t *update_partition = NULL;
  const esp_partition_t *configured = esp_ota_get_boot_partition();
  const esp_partition_t *running = esp_ota_get_running_partition();

  if(configured != running) {
    myDprintf("Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x\n",configured->address,running->address);
    myDprintf("(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)\n");
  }
  myDprintf("Running partition type %d subtype %d (offset 0x%08x)\n",running->type,running->subtype,running->address);
  
  if(running->subtype == 17) curimg = 2; // offset 0x00110000
  else if(running->subtype == 16) curimg = 1; // offset 0x00010000
  else curimg = 0; // ?

  uint32_t img2len=running->size;
  myDprintf("curimg=%d size=%d bytes\n",curimg,running->size);

  int n;
  int s = -1;
  
  if(fwupdateio == 2) {
    if(fatinit()) return -1;
    char *fname=MYMOUNTPT"/fw.bix";
    struct stat st;
    if(stat(fname,&st)) {
      myDprintf("Cant stat |%s|\n",fname);
      return -1;
    }
    n = st.st_size;
    if(myfwfp) fclose(myfwfp);
    if((myfwfp=fopen(fname,"rb")) == NULL) {
      myDprintf("Cant open |%s|\n",fname);
      return -1;
    }
    myDprintf("fw=|%s| %d bytes\n",fname,st.st_size);
    goto dodiskfw;
  }

  struct sockaddr_in addr;
  struct hostent *host;
  char *p;

  if((p=strstr(myparams->fwupdateurl,"//")) == NULL) {
    myDprintf("Invalid URL format |%s|\n",myparams->fwupdateurl);
    return -1;
  }
  p += 2;
  char *fwupdatehost=(char *)mymalloc(strlen(p)+1);
  strcpy(fwupdatehost,p);  
  if((p=strstr(fwupdatehost,"/"))) *p++ = '\0';

  if(fwupdateio == 1) goto skipnet;

  myDprintf("Looking for updated fw from %s\n",myparams->fwupdateurl);
  if(haveinet == 0) {
    myDprintf("Not connected to Net!\n");
    myfree(fwupdatehost);
    return -1;
  }
  if((s=socket(AF_INET,SOCK_STREAM,0)) < 0) {
    myDprintf("Could not connect (socket)\n");
    myfree(fwupdatehost);
    return -1;
  }
  int recv_timeout_ms = RECV_TO;
#if defined(LWIP_SO_SNDRCVTIMEO_NONSTANDARD) && (LWIP_SO_SNDRCVTIMEO_NONSTANDARD == 0)  // lwip 1.5.0
  struct timeval recv_timeout;
  recv_timeout.tv_sec = recv_timeout_ms/1000;
  recv_timeout.tv_usec = recv_timeout_ms % 1000 * 1000;
  setsockopt(s,SOL_SOCKET,SO_RCVTIMEO,&recv_timeout,sizeof(recv_timeout));
#else   // lwip 1.4.1
  setsockopt(s,SOL_SOCKET,SO_RCVTIMEO,&recv_timeout_ms,sizeof(recv_timeout_ms));
#endif // lwip 1.4.1
  addr.sin_family = AF_INET;
  addr.sin_port = htons(80); // http
  if((host=gethostbyname(fwupdatehost)) == NULL) {
    myDprintf("Could not gethost |%s|\n",fwupdatehost);
    myfree(fwupdatehost);
    (*fwupclose)(s);
    return -1;
  }
  memcpy((void *)&addr.sin_addr,(void *)host->h_addr,4);
  if(connect(s,(struct sockaddr *)&addr,sizeof(addr)) != 0) {
    myDprintf("Could not connect to %s\n",pipaddr(addr.sin_addr.s_addr));
    myfree(fwupdatehost);
    (*fwupclose)(s);
    return -1;
  }
  
 skipnet:
  //notconnected_tmo = 0;
  my_watchdog_refresh();
  myDprintf("Connected!\n");

  char c[256];
  int ni;
  uint8_t fst=1;
  snprintf(c,sizeof(c),"GET %s HTTP/1.1\r\nHost: %s\r\n\r\n",myparams->fwupdateurl,fwupdatehost);
  ni = strlen(c); (*fwupwrite)(s,c,ni);
  myfree(fwupdatehost); fwupdatehost = NULL;
  
  n = i = 0;
  while(1) {
    if((*fwupread)(s,&c[i],1) < 1) {
      myDprintf("Connection timed out.\n");
      (*fwupclose)(s);
      return -1;
    }    
    if(c[i] == '\n') {
      if(i == 0 || (i == 1 && c[0] == '\r') ) break; 
      c[i] = '\0'; // \n
      c[i-1] = '\0'; // \r
      if(debug>0) myprintf("|%s|\n",c);
      if(fst) {
        fst = 0;
        if(strcasestr(c," 200 OK") == NULL) {
          myDprintf("html error\n");
          (*fwupclose)(s);
           return -1;
        }
      }
      if((p=strstr(c,"Content-Length: "))) {
        p += strlen("Content-Length: ");
        n = atoi(p);
      }
      i = 0;
      continue;
    }
    if((i+1) < sizeof(c)) i++; // not perfect. lost long lines
  }
  // Why so many "Cannot allocate pbuf to receive packet" when in bridge mode?
  //myDprintf("Header done. %d byte body\n",n);
  snprintf(c,sizeof(c),"Header done. %d byte body\r\n",n);
  ni = strlen(c); (*fwupwrite)(s,c,ni);
  
  if(n == 0) {
    myDprintf("Done - no data\n");
    (*fwupclose)(s);
    return -1;
  }
  
 dodiskfw:
  uint8_t *buf=(uint8_t *)mymalloc(FLASH_SECTOR_SIZE);
  
  if((*fwupread)(s,buf,128) < 128) { // read header
    myDprintf("Could not read fw header\n");
    (*fwupclose)(s);
    myfree(buf);
    return -1;
  }
  if(debug>0) rdump(buf,64);
  n -= 128;
  buf[64] = '\0';

  if(fwupdateio != 1 && strncmp((char *)buf,THISVERSION,strlen(THISVERSION)) == 0) { // skip if console
    myDprintf("Same fw version (%s).  No need to update\n",THISVERSION);
    (*fwupclose)(s);
    myfree(buf);
    return 0;    
  }
  
  if((uint32_t)n >= (img2len+64)) { // limit "n" to max flash size + 64 here?
    myDprintf("Bad fw size %d bytes. Oversized\n",n-64);
    (*fwupclose)(s);
    myfree(buf);
    return 0;
  }
  //myDprintf("Downloading %d bytes into %d byte partition\n",n,img2len);
  snprintf(c,sizeof(c),"Downloading %d bytes into %ld byte partition\r\n",n,img2len);
  ni = strlen(c); (*fwupwrite)(s,c,ni);
  
  if((update_partition=esp_ota_get_next_update_partition(NULL)) == NULL) {
    myDprintf("Cannot get next partition\n");
    (*fwupclose)(s);
    myfree(buf);
    return 0;
  }

  my_watchdog_refresh(); // next step takes long
  
  //myDprintf("Writing to partition subtype %d at offset 0x%x\n",update_partition->subtype,update_partition->address);
  snprintf(c,sizeof(c),"Writing to partition subtype %d at offset 0x%lx\r\n",
	   update_partition->subtype,update_partition->address);
  ni = strlen(c); (*fwupwrite)(s,c,ni);  
#ifndef FWUTEST
  if((err=esp_ota_begin(update_partition,OTA_SIZE_UNKNOWN,&update_handle)) != ESP_OK) {
    //myDprintf("esp_ota_begin failed, error=%d\n",err); // esp_err_to_name(err)
    snprintf(c,sizeof(c),"esp_ota_begin failed, error=%s\n",esp_err_to_name(err));
    ni = strlen(c); (*fwupwrite)(s,c,ni);
    (*fwupclose)(s);
    myfree(buf);
    esp_ota_abort(update_handle);
    return 0;
  }
  //myDprintf("esp_ota_begin succeeded\n");
  snprintf(c,sizeof(c),"esp_ota_begin succeeded\r\n");
  ni = strlen(c); (*fwupwrite)(s,c,ni);  
#endif // FWUTEST
  
  //notconnected_tmo = 0;
  my_watchdog_refresh();
 
  uint32_t curfw=0;

  int myhash32(void *cb,uint8_t *in,int n);
  j = myhash32(NULL,NULL,-1);
  uint8_t k12[j];
  myhash32(k12,NULL,0); // fill in k12
  
  memset(buf,0xFF,FLASH_SECTOR_SIZE);
  j = 0;

  //myDprintf("Start Download\n");
  snprintf(c,sizeof(c),"Start Download\r\n");
  ni = strlen(c); (*fwupwrite)(s,c,ni);

  while(1) {

    //notconnected_tmo = 0;
    my_watchdog_refresh();
    
    if((i=(*fwupread)(s,&buf[j],FLASH_SECTOR_SIZE-j)) <= 0) {
      //myDprintf("read failed\n");
      snprintf(c,sizeof(c),"read failed\r\n");
      ni= strlen(c);
      (*fwupwrite)(s,c,ni);
      (*fwupclose)(s);
      myfree(buf);
#ifndef FWUTEST      
      esp_ota_abort(update_handle); //esp_ota_end(update_handle);
#endif // FWUTEST      
      return -1;
    }
    //myDprintf("Got %d bytes\n",i); // rdump(&buf[j],min(i,64));
    j += i;
    if(j >= FLASH_SECTOR_SIZE) {      
      myhash32(k12,buf,FLASH_SECTOR_SIZE);
      // need some sort of flow control for console mode
      //myDprintf("read a sector. write it to flash 0x%08x\n",curfw);
      snprintf(c,sizeof(c),"read a sector. write it to flash 0x%08lx\r\n",curfw);
      ni = strlen(c); (*fwupwrite)(s,c,ni);
#ifndef FWUTEST
      err = esp_ota_write(update_handle,(const void *)buf,FLASH_SECTOR_SIZE);
      if(err != ESP_OK) {
        myDprintf("Error: esp_ota_write failed! err=0x%x\n",err);
        (*fwupclose)(s);
        myfree(buf);
        esp_ota_abort(update_handle); // esp_ota_end(update_handle);
        return -1;
      }
#endif // FWUTEST      
      j = 0;
      curfw += FLASH_SECTOR_SIZE;
      memset(buf,0xFF,FLASH_SECTOR_SIZE);
    } else {
      snprintf(c,sizeof(c),"read %d bytes\r\n",j);
      ni = strlen(c);
      (*fwupwrite)(s,c,ni);
    }
    n -= i;
    //myprintf("%s: read %d (%d remain)\n",__func__,i,n);
    if(n <= 0) { // buf should only have 64byte sig(hash)
      //myDprintf("%d sig bytes done\n",j);
      snprintf(c,sizeof(c),"%d sig bytes done\r\n",j);
      ni = strlen(c);
      (*fwupwrite)(s,c,ni);
      break;
    }
  }

  //notconnected_tmo = 0;
  my_watchdog_refresh();
  
  uint8_t pubkey[64];
  ahex2bin(pubkey,sizeof(pubkey),myparams->fwupdatepubkey);
  uint8_t thash[32];
  myhash32(k12,NULL,-2); // write over cb with hash
  memcpy(thash,(uint8_t *)k12,32);
  
  //myDprintf("hash=\n"); rdump(thash,32);  
  if(j != 64 || lcled25519_verify(buf,thash,32,pubkey) == 0) {  
    myDprintf("fw corrupted. NOT UPDATING\n");
    (*fwupclose)(s);
    myfree(buf);
#ifndef FWUTEST
    esp_ota_abort(update_handle); // esp_ota_end(update_handle);
#endif // FWUTEST    
    return -1;
  }
  (*fwupclose)(s);
  myfree(buf);

  //notconnected_tmo = 0;
  my_watchdog_refresh();
  
  //myDprintf("fw validated.\n");
  snprintf(c,sizeof(c),"fw validated.\r\n");
  ni = strlen(c); (*fwupwrite)(s,c,ni);    
#ifndef FWUTEST
  if(esp_ota_end(update_handle) != ESP_OK) {
    myDprintf("esp_ota_end failed!\n");
    return -1;    
  }
  //return 0; // for testing  
  app_end(); // Could shutdown wifi, tasks, etc here to be nice

  err = esp_ota_set_boot_partition(update_partition);
  if(err != ESP_OK) {
    myDprintf("esp_ota_set_boot_partition failed! err=0x%x\n",err);
    return -1;
  }
#endif // FWUTEST
  
  printf("Prepare to restart system!\n"); // note use of RAW printf

  //if(mywatchdog_th) { vTaskDelete(mywatchdog_th); mywatchdog_th = NULL; }
  //if(receive_task_th) { vTaskDelete(receive_task_th); receive_task_th = NULL; }
  //if(rlprintf_th) { vTaskDelete(rlprintf_th); rlprintf_th = NULL; }

  mysleep(1);

  //esp_task_wdt_delete(NULL);
#ifndef FWUTEST  
  esp_restart();
#endif // FWUTEST

  return 0;  
}
/*end copyright*/
static uint8_t fwupdate_running=0;
static void fwupdate_task(void *arg)
{
  mysetupwtchdog(120);
  disable_spi_ops = 1; // need to stop any other tasks doin spi ops like lvgl/fatfs
  if(arg) {
    if(strcmp((char *)arg,"console") == 0) { // fwupdate via console
      myconsole_logok = 0; // no myprintf output
      consoleok = 0; // no console input
      mysleep(1);
      fwupdate(1);
      consoleok = 1;
      myconsole_logok = 1;
    } else if(strcmp((char *)arg,"disk") == 0) {
      fwupdate(2);
    }
  } else { // fwupdate via 'net
    fwupdate(0);
  }
  disable_spi_ops = 0;
  esp_task_wdt_delete(NULL);
  fwupdate_running = 0;
  vTaskDelete(NULL); // NULL or xTaskGetCurrentTaskHandle()  
}
static int dofwupdate(int argc,char *argv[])
{
  void *arg=NULL;
  if(fwupdate_running) {
    myprintf("fwupdate already running\n");
    return 0;
  }
  if(argc > 1 && argv[1]) arg = argv[1];
  fwupdate_running = 1;
  if(xTaskCreate(fwupdate_task,"fwupdate",5120,arg,(tskIDLE_PRIORITY+1),NULL) != pdPASS) {
    // big stack (5120x4) for crypto calls 16000x4 is max
    fwupdate_running = 0;
    myDprintf("Could not create fwupdate task\n");
  }
  return 0;
}


#ifdef DO_GPS
static void gpsstats(void);
static int dogps(int argc,char *argv[]);
static const int mymt[]={0,31,59,90,120,151,181,212,243,273,304,334};
static uint32_t date2secs(int y,int m,int d,int h,int M,int s)
{
  uint32_t t;
  // date to seconds since epoch
  t = (y-1)/4 - ((1970-1)/4) - (y-1)/100 + ((1970-1)/100) + (y-1)/400 - ((1970-1)/400);
  t += 365*(y-1970);
  t += mymt[m];
  if(m > 0 && ((y%4 == 0)&&((y%100 != 0)||(y%400 == 0)))) t++;
  t += d;
  t = s + (60*M) + (3600*h) + (86400*t);
  return t;
}
#endif // DO_GPS

/*
 * my show stats for the system
 *
 * Copyright notice:
 * SPDX-FileCopyrightText: Copyright (c) 2025-2026 Richard Lamb
 * SPDX-License-Identifier: CC BY-NC-SA-4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
 * Author: RHLamb (W3TOY) wwvb@xtcn.com
 *
 * The above copyright notice shall be included in all copies or substantial portions of this software.
 */
#include "esp_system.h"
#include "esp_mac.h"
#include "esp_ota_ops.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_psram.h"
#include "nvs.h"
#include "soc/rtc.h"
static void adcstats(void);
static void wwvbhourly(char *out);

static float lvdelayms=0;
static float uidelayms=0;

static esp_reset_reason_t resetreason; // read at boot
static char *resetreasonstr(esp_reset_reason_t i)
{
  switch(i) {
  case ESP_RST_UNKNOWN: return "Reset reason can not be determined.";
  case ESP_RST_POWERON: return "Reset due to power-on event.";
  case ESP_RST_EXT: return "Reset by external pin (not applicable for ESP32)";
  case ESP_RST_SW: return "Software reset via esp_restart.";
  case ESP_RST_PANIC: return "Software reset due to exception/panic.";
  case ESP_RST_INT_WDT: return "Reset (software or hardware) due to interrupt watchdog.";
  case ESP_RST_TASK_WDT: return "Reset due to task watchdog.";
  case ESP_RST_WDT: return "Reset due to other watchdogs.";
  case ESP_RST_DEEPSLEEP: return "Reset after exiting deep sleep mode.";
  case ESP_RST_BROWNOUT: return "Brownout reset (software or hardware)";
  case ESP_RST_SDIO: return "Reset over SDIO.";
  case ESP_RST_USB: return "Reset by USB peripheral.";
  case ESP_RST_JTAG: return "Reset by JTAG.";
  case ESP_RST_EFUSE: return "Reset due to efuse error.";
  case ESP_RST_PWR_GLITCH: return "Reset due to power glitch detected.";
  case ESP_RST_CPU_LOCKUP: return "Reset due to CPU lock up (double exception)";
  default: return "Unknown";
  }
}
static int mystats(void)
{
  int i;
  char lbuf[128];

  time_t ltmp;
  time(&ltmp);
  myprintf("%s  %s",FINDABLEVERSION,ctime(&ltmp));

  {
    /* Print chip information */
    esp_chip_info_t chip_info;
    uint32_t flash_size;
    esp_chip_info(&chip_info);
    //myprintf("model:%d features=%08X\n",chip_info.model,chip_info.features);
    myprintf("This is %s chip with %d CPU core(s), WiFi%s%s%s, ",
             CONFIG_IDF_TARGET,chip_info.cores,
             (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
             (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "",
             (chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : ""
             );
    unsigned major_rev = chip_info.revision / 100;
    unsigned minor_rev = chip_info.revision % 100;
    myprintf("silicon revision v%d.%d\n", major_rev, minor_rev);
    if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
      myprintf("Get flash size failed\n");
    }
    myprintf("model:%d features=%08X idf:%s\n",chip_info.model,(unsigned int)chip_info.features,IDF_VER);
#ifdef CONFIG_SPIRAM    
    myprintf("%dMB %s flash, %dMB %s psram ",
             flash_size/(uint32_t)(1024*1024),
             (chip_info.features&CHIP_FEATURE_EMB_FLASH)?"embedded":"ext",
             esp_psram_get_size()/(uint32_t)(1024*1024),
             (chip_info.features&CHIP_FEATURE_EMB_PSRAM)?"embedded":"ext");
#else // CONFIG_SPIRAM
  myprintf("%dMB %s flash, %dMB %s psram ",
           flash_size/(uint32_t)(1024*1024),
           (chip_info.features&CHIP_FEATURE_EMB_FLASH)?"embedded":"ext",
           0,
           (chip_info.features&CHIP_FEATURE_EMB_PSRAM)?"embedded":"ext");
#endif // CONFIG_SPIRAM
  }

  rtc_slow_freq_t slow_clk = rtc_clk_slow_freq_get(); // rtc_clk_slow_src_get()
  myprintf("RTC clk:");
  switch(slow_clk) {
  case RTC_SLOW_FREQ_RTC: myprintf("Internal 150kHz "); break;
  case RTC_SLOW_FREQ_32K_XTAL: myprintf("External 32kHz "); break;
  case RTC_SLOW_FREQ_8MD256: myprintf("Internal 8MHz "); break;
  case SOC_RTC_SLOW_CLK_SRC_INVALID: myprintf("INVALID "); break;
  }

  esp_efuse_mac_get_default(mymac);
  myprintf("mac:%02X:%02X:%02X:%02X:%02X:%02X\n",mymac[0],mymac[1],mymac[2],mymac[3],mymac[4],mymac[5]);

  myprintf("%s\n",getcurimg());
  myprintf("heap:%ld/%ld(min) mymalloc:%ld/%ld(max)\n",
           esp_get_free_heap_size(),esp_get_minimum_free_heap_size(),memtot,memmax);
  
  uint32_t rs = (uint32_t)((esp_timer_get_time() - mystarttime)/1000000);
  
  if(rs > 86400) sprintf(lbuf,"%d:days %d:hours",(int)(rs/86400),(int)(rs%86400)/3600);
  else sprintf(lbuf,"%d:hrs %d:mins",(int)(rs/3600),(int)((rs%3600)/60));
  myprintf("Uptime:%s Last %s lv=%.0f ui=%.0f\n",lbuf,resetreasonstr(resetreason),lvdelayms,uidelayms);
  
  if(debug > 0) {
    nvs_stats_t stat1;
    if(nvs_get_stats(NULL,&stat1) == 0) {
      myprintf("NVS: free_entries:%d namespace_count:%d total_entries:%d used_entries:%d\n",
               stat1.free_entries,stat1.namespace_count,stat1.total_entries,stat1.used_entries);
      // free:473 nscnt:4 total:504 used:31                     
    }
  }

  int xi=uxTaskGetNumberOfTasks();
  {
    TaskStatus_t *xp=(TaskStatus_t *)mymalloc(xi*sizeof(TaskStatus_t));
    unsigned long ulTotalRunTime;
    xi = uxTaskGetSystemState(xp,xi,&ulTotalRunTime );    
    int j,cpuno=-1;
    char nbuf[32];
#define MYTFMTSTR "%-11s %6d  %2d  %2d  %2d\n"    
    myprintf("name        ustack pri num cpu\n");
    for(i=0;i<xi;i++) {
      TaskStatus_t *ts= xp + i;      
      j = min(strlen(ts->pcTaskName),11);
      strncpy(nbuf,ts->pcTaskName,j);
      nbuf[j] = '\0';
      // ts->usStackHighWaterMark is in configSTACK_DEPTH_TYPE
      j = ts->usStackHighWaterMark;
      //j = uxTaskGetStackHighWaterMark2(ts->xHandle);
      j *= sizeof(configSTACK_DEPTH_TYPE);
#if configTASKLIST_INCLUDE_COREID
      cpuno = (int)ts->xCoreID; // 0,1, or 0x7fffffff
#else
      cpuno = xTaskGetCoreID(ts->xHandle);
#endif // configTASKLIST_INCLUDE_COREID
      if(cpuno > 10 || cpuno < 0) cpuno = -1;
      myprintf(MYTFMTSTR,nbuf,j,ts->uxCurrentPriority,ts->xTaskNumber,cpuno);
    }    
    myfree(xp);
  }

  taskYIELD();  
#if configUSE_TIME_SLICING_foop
  dont use configUSE_TIME_SLICING
#endif // configUSE_TIME_SLICING
#if configUSE_PREEMPTION_foop
  dont use configUSE_PREEMPTION
#endif // configUSE_PREEMPTION
  
  myprintf("mode=%d tz=%s\n",config_mode,config_tz);
  // wwvb stats
  adcstats();  
  myprintf("lofreq=%.2f adcclk=%.2f\n",cur_lo_freq,cur_fs_freq);
  char out[132];
  wwvbhourly(out);
  myprintf("%s\n",out);

#ifdef DO_GPS
  gpsstats();
#endif // DO_GPS  
  return 0;
}

/*
 * my old console stuff
 *
 * Copyright notice:
 * SPDX-FileCopyrightText: Copyright (c) 2025-2026 Richard Lamb
 * SPDX-License-Identifier: CC BY-NC-SA-4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
 * Author: RHLamb (W3TOY) wwvb@xtcn.com
 *
 * The above copyright notice shall be included in all copies or substantial portions of this software.
 */
static int domystats(int argc,char *argv[])
{
  mystats();
  //if(mysdcard) sdmmc_card_print_info(stdout,mysdcard);
  return 0;
}
static int dosetget(int argc,char *argv[])
{
  if(argc < 2) {
    myprintf("rotate:%d\n",rotatedisp);
    return 0;
  }
  if(strcmp(argv[1],"rotate") == 0) {
    if(argc > 2) rotatedisp = atoi(argv[2]);
    myprintf("rotate:%d\n",rotatedisp);
    if(dodiskstuff == NULL) dodiskstuff = mystrdup("update");
  } else {
  }
  return 0;
}

#ifdef DO_WIFI
static int dowifi(int argc,char *argv[]);
static int dontp(int argc,char *argv[]);
#endif // DO_WIFI
static int dofreq(int argc,char *argv[]);
static int dotest(int argc,char *argv[]);
static int doreset(int argc,char *argv[]) { myreset(); return 0; }
static int go(int argc,char *argv[]) { return 0; }
static int doexit(int argc,char *argv[]) { return 3; }
static int dohelp(int argc,char *argv[]);
struct command {
  char *name;
  int (*function)(int argc,char *argv[]);
};
static struct command cmds[] = {
  "",go, /* first */
  "?",dohelp,
  "exit",doexit,
  "status",domystats,
  "setget",dosetget,
  "reset",doreset,
  "fwupdate [console|disk]",dofwupdate,
  "format",doformat,
  "dir",dodir,
  "del f",dodel,
  "fdump f",dofdump,
  "mount",domount,
  "umount",doumount,
  "i2cscan",myi2cscan,
  "test",dotest,
  "freq [loMhz [fsMHz]]",dofreq,
#ifdef DO_GPS
  "gps",dogps,
#endif // DO_GPS
#ifdef DO_WIFI
  "wifi [start|stop|ssid x|pass x]",dowifi,
  "ntp [start|stop|server 1.2.3.4]",dontp,
#endif // DO_WIFI  
  (char *)0,NULL,
};
static int dohelp(int argc,char *argv[]) {
  struct command *cm;
  for(cm=cmds;cm->name;cm++) if(strlen(cm->name) > 0) myprintf(" %s\n",cm->name);
  return 0;
}
#define COMMAND_MAX_ARGS 20
static int cparse(char *line)
{
  struct command *cm;
  char *argv[COMMAND_MAX_ARGS];
  int argc;
  myprintf("%s\n",line);
  argc = lparse(line,argv,COMMAND_MAX_ARGS,' ');
  if(argv[0] == (char *)0) return 0;
  if(argv[0][0] == '#') return 0; // comments
  for(cm=cmds;cm->name;cm++) {
    if(strncmp(argv[0],cm->name,strlen(argv[0])) == 0) break; // partial match
  }
  if(cm->name == (char *)0) {
    myprintf("Unknown Command\n");
    return -1; /* No command */
  }
  return (*cm->function)(argc,argv);
}

/*
 * my console I/O
 *
 * Copyright notice:
 * SPDX-FileCopyrightText: Copyright (c) 2025-2026 Richard Lamb
 * SPDX-License-Identifier: CC BY-NC-SA-4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
 * Author: RHLamb (W3TOY) wwvb@xtcn.com
 *
 * The above copyright notice shall be included in all copies or substantial portions of this software.
 */
#ifdef CONFIG_IDF_TARGET_ESP32S2
//#define DOTINYUSB_201_1  // tinyusb 2 does not work. drops bytes.
/*
  esp32s2 v1.001:    espressif/esp_tinyusb (1.0.3) espressif/tinyusb (0.19.0~2)
  (main/idf_component.yml: espressif/esp_tinyusb: "~1.0.0")
  esp32s2old v1.000: espressif/esp_tinyusb (1.7.6) espressif/tinyusb (0.18.0~2)
 */
#ifdef DOTINYUSB_201_1
#include "tinyusb_default_config.h"
#include "tinyusb_cdc_acm.h"
#include "tinyusb_console.h"
#else // DOTINYUSB_201_1
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
#include "tusb_console.h"
#endif // DOTINYUSB_201_1

static char cdc0serial[80];
typedef const char *tusb_desc_strarray_device_t[8];
static tusb_desc_strarray_device_t cdc0strings = {
  (char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
  "Dept C Inc",  // 1: Manufacturer
  "WWVB", // 2: Product
  cdc0serial, // 3: Serials, should use chip ID
  "TinyUSB CDC",
  "TinyUSB WebUSB",
  "TinyUSB MSC",
  "TinyUSB HID",
};
#ifdef DOTINYUSB_201_1 // try rx_callback for speed. didnt make a difference!
static uint8_t cdcrx0[4352];
static int cdcrx0head=0,cdcrx0tail=0;
static atomic_int cdcrx0n=0;
static void cdc_rx_callback(int itf,cdcacm_event_t *event)
{
  size_t n;
  int k=0,rxn=cdcrx0n;
  size_t j=(sizeof(cdcrx0)-rxn);
  while(j > 0) {
    n = 0;
    tinyusb_cdcacm_read(itf,&cdcrx0[cdcrx0tail],1,&n);
    if(n == 0) break;
    cdcrx0tail = (cdcrx0tail + 1)%sizeof(cdcrx0);
    rxn++;
    j--;
    k++;
  }
  cdcrx0n = rxn;
  // printf("%s %d\n",__func__,k);
}
#endif // DOTINYUSB_201_1
#endif // CONFIG_IDF_TARGET_ESP32S2

#include "driver/uart.h"
#define UART0BAUD 115200
static int (*uart0respproc)(char *in,int state);
static uint8_t uart0cont=0;
static int console_init=1;
static void console_proc(void)
{
  if(console_init) {
#ifdef CONFIG_IDF_TARGET_ESP32S3
    uart_config_t uart_config = {
      .baud_rate = UART0BAUD,
      .data_bits = UART_DATA_8_BITS,
      .parity    = UART_PARITY_DISABLE, // for outside no parity
      .stop_bits = UART_STOP_BITS_1,
      .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
    };
    uart_param_config(UART_NUM_0,&uart_config);
    // use default UART0 pins
    uart_driver_install(UART_NUM_0,256,0,0,NULL,0); // (uart,rxbuflen,txbuflen,qsize,queue,0);
    //uart_driver_install(EX_UART_NUM,UBUF_SIZE * 2, BUF_SIZE * 2,100, &uart0_queue,0);
#endif // CONFIG_IDF_TARGET_ESP32S3
    
#ifdef CONFIG_IDF_TARGET_ESP32S2
    esp_efuse_mac_get_default(mymac);
    sprintf(cdc0serial,"%02x%02x%02x%02x%02x%02x",mymac[0],mymac[1],mymac[2],mymac[3],mymac[4],mymac[5]);
#ifdef DOTINYUSB_201_1 // try rx_callback for speed. didnt make a difference!    
    tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG();
    tusb_cfg.port = TINYUSB_PORT_FULL_SPEED_0; // FULL=1.1 HIGH=2.0
    tusb_cfg.descriptor.string = cdc0strings;
    tusb_cfg.descriptor.string_count = 4;    
    ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
    const tinyusb_config_cdcacm_t acm_cfg = {
      .cdc_port = TINYUSB_CDC_ACM_0,
      //.rx_unread_buf_sz = 64, // tinyusb is a moving target. used to work
      .callback_rx = &cdc_rx_callback, // didnt make a difference!
      //.callback_rx = NULL,
      .callback_rx_wanted_char = NULL,
      .callback_line_state_changed = NULL,
      .callback_line_coding_changed = NULL
    };
    ESP_ERROR_CHECK(tinyusb_cdcacm_init(&acm_cfg));
    tinyusb_console_init(0);
#else // DOTINYUSB_201_1
    const tinyusb_config_t tusb_cfg = {
      .device_descriptor = NULL,
      .string_descriptor = cdc0strings,      
      //.string_descriptor_count = 4, // tinyusb changed again
      .external_phy = false,
      .configuration_descriptor = NULL,
    };
    ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
    tinyusb_config_cdcacm_t acm_cfg = {
      .usb_dev = TINYUSB_USBDEV_0,
      .cdc_port = TINYUSB_CDC_ACM_0,
      .rx_unread_buf_sz = 4096,  // use FLASH_SECTOR_SIZE to support fwupdate() 256 64 
      .callback_rx = NULL,
      .callback_rx_wanted_char = NULL,
      .callback_line_state_changed = NULL,
      .callback_line_coding_changed = NULL
    };
    ESP_ERROR_CHECK(tusb_cdc_acm_init(&acm_cfg));
    esp_tusb_init_console(0);
#endif // DOTINYUSB_201_1 
#endif // CONFIG_IDF_TARGET_ESP32S2
    
    console_init = 0;
  }

  static int i=0;
  static char lbuf[1024];
  int c;
  size_t n=0;
  char dbuf[256];
#if defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C3)
  n = usb_serial_jtag_read_bytes(dbuf,sizeof(dbuf),1);
#endif

#ifdef CONFIG_IDF_TARGET_ESP32S2
#ifdef DOTINYUSB_201_1 // try rx_callback for speed. didnt make a difference!
  int j=0,rxn=cdcrx0n;
  while(rxn > 0 && j < sizeof(dbuf)) {
    dbuf[j] = cdcrx0[cdcrx0head];
    cdcrx0head = (cdcrx0head + 1)%sizeof(cdcrx0);
    rxn--;
    j++;
  }
  cdcrx0n = rxn;
  n = j;
#else // DOTINYUSB_201_1  
  tinyusb_cdcacm_read(TINYUSB_CDC_ACM_0,(uint8_t *)dbuf,sizeof(dbuf),&n);
#endif // DOTINYUSB_201_1  
#endif

#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3)
  //uart_get_buffered_data_len(UART_NUM_0,(size_t*)&n);
  n = uart_read_bytes(UART_NUM_0,dbuf,sizeof(dbuf),0);
#endif
  for(int k=0;k<n;k++) {
    c = (int)dbuf[k];
    if(c == '\r' || c == '\n') {
      if(uart0cont) {
	lbuf[i] = '\0';
	if(i == 0 || (i > 0 && lbuf[0] != ' ')) {
	  uart0cont = 0;
	  if(uart0respproc) {
	    //consoleok = 1; // myconsole_logok=1
	    uart0respproc(lbuf,2); // end
	    uart0respproc = NULL;
	  }
	} else if(i > 1) {
	  if(uart0respproc) uart0respproc(&lbuf[1],1); // middle
	}
      } else { // console
	lbuf[i] = '\0';      
	cparse(lbuf);
      }    
      i = 0;
    } else {
      if(i < (int)(sizeof(lbuf)-1)) lbuf[i++] = (char)c;
    }
  }

  
}
static ssize_t consoleread(int s,void *p,size_t n)
{
#if defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C3)
  int k = usb_serial_jtag_read_bytes(p,n,1000/portTICK_PERIOD_MS);
  return (ssize_t)k;
#endif

#ifdef CONFIG_IDF_TARGET_ESP32S2
#ifdef DOTINYUSB_201_1 // try rx_callback for speed. didnt make a difference!
  uint8_t *cdc0_cur_rxbuf = (uint8_t *)p;
  size_t cdc0_cur_rxbuf_len = n;
  int j=0,jj=0;
  while(jj++ < 500) {
    int k=cdcrx0n;
    while(k > 0 && cdc0_cur_rxbuf_len > 0) {
      cdc0_cur_rxbuf[j] = cdcrx0[cdcrx0head];
      cdcrx0head = (cdcrx0head + 1)%sizeof(cdcrx0);
      k--;
      j++;
      cdc0_cur_rxbuf_len--;
    }
    cdcrx0n = k;
    if(cdc0_cur_rxbuf_len == 0) break;
    vTaskDelay(1);
  }
  if(n != j) printf("%s err:%d/%d\n",__func__,n,j);
  return j;  
#else // DOTINYUSB_201_1  
  uint8_t *cdc0_cur_rxbuf = (uint8_t *)p;
  size_t cdc0_cur_rxbuf_len = n;
  int jj=0;
  while(cdc0_cur_rxbuf_len > 0 && jj++ < 100) {
    vTaskDelay(1);
    size_t kk=0;
    tinyusb_cdcacm_read(TINYUSB_CDC_ACM_0,cdc0_cur_rxbuf,cdc0_cur_rxbuf_len,&kk);
    cdc0_cur_rxbuf += kk;
    cdc0_cur_rxbuf_len -= kk;
  }
  jj = (int)(cdc0_cur_rxbuf - (uint8_t *)p);
  //if(n != jj) printf("%s err:%d/%d\n",__func__,n,jj);
  return (n - cdc0_cur_rxbuf_len);
#endif // DOTINYUSB_201_1  
#endif // CONFIG_IDF_TARGET_ESP32S2

#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3)
  int k = uart_read_bytes(UART_NUM_0,p,n,1000/portTICK_PERIOD_MS);
  return (ssize_t)k;
#endif  
}
#ifdef CONFIG_IDF_TARGET_ESP32S2
static void cdc0_write(uint8_t *p,int n)
{
  int i;
  if(console_init) return;
  while(n > 0) {
    i = min(n,64);
    tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_0,p,i);
    tinyusb_cdcacm_write_flush(TINYUSB_CDC_ACM_0,1);    
    p += i;
    n -= i;
  }
}
#endif // CONFIG_IDF_TARGET_ESP32S2
static ssize_t consolewrite(int s,const void *p,size_t n)
{
  return write(1,p,n);
}
static int consoleclose(int s)
{
  return 0;
}

/*
 * BLE
 */
// someday soon

/*
 * my printf
 *
 * Copyright notice:
 * SPDX-FileCopyrightText: Copyright (c) 2025-2026 Richard Lamb
 * SPDX-License-Identifier: CC BY-NC-SA-4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
 * Author: RHLamb (W3TOY) wwvb@xtcn.com
 *
 * The above copyright notice shall be included in all copies or substantial portions of this software.
 */
static void lcdconsole(char *in); // if LCD has debug output
#define NOWAITPRINTF
#ifdef NOWAITPRINTF
typedef struct _rlpq {
  struct _rlpq *next;
  int n;
} rlpq;
static rlpq *rlpqhead=NULL,*rlpqtail=NULL;
#define NMAXRLPQ 128  // max number of printf's queued before drop
static int rlpqcnt=0;
static int rlpq_add(uint8_t *p,int n)
{
  rlpq *x;
  if(rlpqcnt >= NMAXRLPQ) return -1; // drop
  if((x=(rlpq *)mymalloc(sizeof(rlpq)+n)) == NULL) return -2; // drop
  memset(x,0,sizeof(rlpq));
  x->n = n;
  memcpy(x+1,p,n);
  if(xSemaphoreTake(rlprintfmux,RLPRINTFMUXTMO) != pdTRUE) {
    printf("%s: Could NOT get mux.  Dropping output\n",__func__);
    myfree(x);
    return -1;
  }
  if(rlpqhead == NULL) {
    rlpqhead = rlpqtail = x;
  } else {
    rlpqtail->next = x;
    rlpqtail = x;
  }
  rlpqcnt++;
  xSemaphoreGive(rlprintfmux);
  return 0;
}
static void rlprintf_proc(void)
{
  while(rlpqhead) { // do multiple
    if(xSemaphoreTake(rlprintfmux,RLPRINTFMUXTMO) != pdTRUE) {
      printf("%s: Could NOT get mux. Skip queue processing.\n",__func__);
      return;
    }    
    rlpq *x=rlpqhead;
    rlpqhead = x->next;
    if(rlpqhead == NULL) rlpqtail = NULL;
    rlpqcnt--;
    xSemaphoreGive(rlprintfmux);

#ifdef CONFIG_IDF_TARGET_ESP32S2
    char *p=(char *)(x+1);
    cdc0_write((uint8_t *)p,x->n);
#endif // CONFIG_IDF_TARGET_ESP32S2 
#ifdef CONFIG_IDF_TARGET_ESP32S3
    printf("%s",(char *)(x+1)); // note use of RAW printf
#endif // CONFIG_IDF_TARGET_ESP32S3
    
    lcdconsole((char *)(x+1)); // takes long time
    
    myfree(x);
  }

}
#endif // NOWAITPRINTF
static uint8_t rlprintf_buffer[1024];
void rlprintf(const char *fnc,const char *format, ...)
{
  va_list args;
  int n,i;
  uint8_t *p;

  if(myconsole_logok == 0) { // just drop it "consoleok"?
    return;
  }
  static SemaphoreHandle_t inuse=NULL;
  if(inuse == NULL) inuse = xSemaphoreCreateMutex();
  if(xSemaphoreTake(inuse,RLPRINTFMUXTMO) != pdTRUE) {
    printf("%s: inuse from %s. Dropping.\n",__func__,fnc?fnc:"NULL"); // note use of RAW printf
    return;
  }
  
  if(fnc) {
    p = rlprintf_buffer;
    p += sprintf((char *)p,"%s: ",fnc);
    i = sizeof(rlprintf_buffer) - 1;
    i -= (int)(p - rlprintf_buffer);
  } else {
    p = rlprintf_buffer;
    i = sizeof(rlprintf_buffer) - 1;
  }
  va_start(args,format);
  n = vsnprintf((char *)p,i,format,args);
  p[n] = '\0';
  va_end(args);


#ifdef NOWAITPRINTF

#ifdef CONFIG_IDF_TARGET_ESP32S2
  if(n > 0 && p[n-1] == '\n') { // crlf for usb cdc/acm
    p[n-1] = '\r';
    p[n] = '\n';
    p[n+1] = '\0';
    n++;
  }
#endif // CONFIG_IDF_TARGET_ESP32S2

  //#define DOMYDEBUG
#ifdef DOMYDEBUG
#ifdef CONFIG_IDF_TARGET_ESP32S2
  cdc0_write(rlprintf_buffer,strlen((char *)rlprintf_buffer));
#else // CONFIG_IDF_TARGET_ESP32S2
  printf("%s",rlprintf_buffer); // note use of RAW printf
#endif // CONFIG_IDF_TARGET_ESP32S2
#else // DOMYDEBUG
  rlpq_add(rlprintf_buffer,strlen((char *)rlprintf_buffer)+1); // slow out
#endif // DOMYDEBUG
  
#else // NOWAITPRINTF  
  printf("%s",rlprintf_buffer); // note use of RAW printf  
  //lcdconsole((char *)rlprintf_buffer); // too slow
#endif // NOWAITPRINTF

  xSemaphoreGive(inuse);
  
  return;
}

/*********************************************************************
 * I2S processing and AIC3254
 *********************************************************************/
// external MCLK=819.2kc, bclk=204.8kc, wclk=3.2kc
// bclk = 64*wclk  mclk = 4*bclk. 400*8 = 3200 samples/sec

#define AIC3204_ADDR 0x18
static i2c_master_dev_handle_t aic3204_dev;
static int aic3204_write_reg(uint8_t addr,uint8_t data)
{
  static uint8_t init=1;
  if(init) {
    if(init == 2) return -1;
    if(myi2ccheck(AIC3204_ADDR) == 0) {
      init = 2;
      myDprintf("No AIC3204 found\n");
      return -1;
    }
    init = 0;
    mydelay(20);
    if(myi2cinitdev(AIC3204_ADDR,0,&aic3204_dev)) {
      init = 2;
      myDprintf("failed init\n");
      return -1;
    }
    mydelay(20);
  }
  uint8_t buf[2];
  buf[0] = addr;
  buf[1] = data;
  return myi2cwrite(aic3204_dev,buf,2);
}
#ifdef FOOP
static uint8_t aic3204_read_reg(uint8_t addr)
{
  uint8_t buf[2],rbuf[16];
  buf[0] = addr;
  memset(rbuf,0,sizeof(rbuf));
  if(i2c_master_transmit_receive(aic3204_dev,buf,1,rbuf,1,1000)) myDprintf("failed\n");
  return rbuf[0];
}
#endif // FOOP
static int aic_init(void)
{
  static uint8_t init=1;
  if(init == 0) return 0;

  //aic3204_reset();  AIC reset PIN
  
  aic3204_write_reg(0x00,0x00); //select Page 0
  
  aic3204_write_reg(0x01,0x01); //Software Reset
  vTaskDelay(10/portTICK_PERIOD_MS);

  // Clock Settings  fsample=CODEC_CLKIN/(NADCxMADCxAOSR)
  aic3204_write_reg(0x19,0x00); //Select CODEC_CLKIN = MCLK
  aic3204_write_reg(0x1a,0x81); //Set CLKOUT_M Divider Active,=1
  aic3204_write_reg(0x1b,0x3c); // i2s,32bit,bclk out,wclk out=00111100
  aic3204_write_reg(0x1d,0x02); // bdiv_clkin=adc_clk
  aic3204_write_reg(0x1e,0x84); // bclk N=4

  aic3204_write_reg(0x0b,0x81); //Power up the NDAC divider with value 1 
  aic3204_write_reg(0x0c,0x82);  //Power up the MDAC divider with value 2

  aic3204_write_reg(0x12,0x81); // Power up NADC divider with value 1  
  aic3204_write_reg(0x13,0x82); // Power up MADC divider with value 2
  
  //Program the OSR of DAC to 128
  aic3204_write_reg(0x0d,0x00);
  aic3204_write_reg(0x0e,0x80); // 0x80
  
  aic3204_write_reg(0x14,0x80); // Program AOSR for ADC to 128
  
  // Digital Section
  //Set I2S, 16bit Data Word, BCLK and WCLK is input
  //aic3204_write_reg(0x1b,0x00);
  //Set I2S, 32bit Data Word, BCLK and WCLK is input
  //aic3204_write_reg(0x3b,0x00);  // write only default
  //aic3204_write_reg(0x1b,0x30); // 32-bit  Already set above
  aic3204_write_reg(0x3c,0x08); // 0x08  Set the DAC Mode to PRB_P8  
  aic3204_write_reg(0x3d,0x01); // Select ADC PRB_R1
  
  // Power and Analog
  aic3204_write_reg(0x00,0x01); //Select Page 1

  //Disable Internal Crude AVdd in presence of external AVdd supply or before
  aic3204_write_reg(0x01,0x08); //powering up internal AVdd LDO 
  aic3204_write_reg(0x02,0x01); //Enable Master Analog Power Control
  aic3204_write_reg(0x7b,0x01); //Set the REF charging time to 40ms
  //HP soft stepping settings for optimal pop performance at power up
  //Rpop used is 6k with N = 6 and soft step = 20usec. This should work with 47uF coupling
  //capacitor. Can try N=5,6 or 7 time constants as well. Trade-off delay vs “pop” sound.
  aic3204_write_reg(0x14,0x25);
  //Set the Input Common Mode to 0.9V and Output Common Mode for Headphone to
  //Input Common Mode
  aic3204_write_reg(0x0a,0x00);
  
  // Analog Routing
  aic3204_write_reg(0x0c,0x08); //Route Left DAC to HPL
  aic3204_write_reg(0x0d,0x08); //Route Right DAC to HPR
  aic3204_write_reg(0x0e,0x08); //Route Left DAC to LOL
  aic3204_write_reg(0x0f,0x08); //Route Right DAC to LOR 
  //Set the DAC PTM mode to PTM_P3/4
  aic3204_write_reg(0x03,0x00);
  aic3204_write_reg(0x04,0x00);  
  aic3204_write_reg(0x3d,0x00); // Select ADC PTM_R4
  aic3204_write_reg(0x10,0x3a); //Set the HPL gain to -6dB
  aic3204_write_reg(0x11,0x3a); //Set the HPR gain to -6dB
  aic3204_write_reg(0x12,0x3a); //Set the LOL gain to -6dB
  aic3204_write_reg(0x13,0x3a); //Set the LOR gain to -6dB
  aic3204_write_reg(0x09,0x3c); //Power up HPL and HPR, LOL and LOR drivers
  //Wait for 2.5 sec for soft stepping to take effect
  //Else read Page 1, Register 63d, D(7:6). When = “11” soft-stepping is complete
  vTaskDelay(2500/portTICK_PERIOD_MS);
  aic3204_write_reg(0x47,0x32); // Set MicPGA startup delay to 3.1ms
  aic3204_write_reg(0x7b,0x01); // Set the REF charging time to 40ms 
  
  aic3204_write_reg(0x34,0x80); // Route IN1L to LEFT_P with 20K input impedance
  aic3204_write_reg(0x36,0x80); // Route Common Mode to LEFT_M with impedance of 20K
  
  aic3204_write_reg(0x37,0x80); // Route IN1R to RIGHT_P with input impedance of 20K
  aic3204_write_reg(0x39,0x80); // Route Common Mode to RIGHT_M with impedance of 20K
  
  // Unmute Left MICPGA, Gain selection of 6dB to make channel gain 0dB
  // Register of 6dB with input impedance of 20K => Channel Gain of 0dB
  aic3204_write_reg(0x3b,0x0c);
  // Unmute Right MICPGA, Gain selection of 6dB to make channel gain 0dB
  // Register of 6dB with input impedance of 20K => Channel Gain of 0dB
  aic3204_write_reg(0x3c,0x0c);
  
  aic3204_write_reg(0x00,0x00); //Select Page 0

  // ..OR.. AGC instead? switch to page 0 left:0x56-5D right:0x5E-0x65 eg:56,5e:80 57,5f:C0
  // 0x24=adc flag reg status, 0x2d noise, 0x5D/65=current micpga gain 
  
  //Power up the Left and Right DAC Channels with route the Left Audio digital data to
  //Left Channel DAC and Right Audio digital data to Right Channel DAC
  aic3204_write_reg(0x3f,0xd6);
  aic3204_write_reg(0x51,0xc0); // Power up Left and Right ADC Channels 
  aic3204_write_reg(0x40,0x00); //Unmute the DAC digital volume control and enable syncronized control (use L-ch)
  aic3204_write_reg(0x52,0x00); // Unmute Left and Right ADC Digital Volume Control.

  init = 0;
  
  return 0;
}

/*
 * Copyright notice:
 * SPDX-FileCopyrightText: Copyright (c) 2025-2026 Richard Lamb
 * SPDX-License-Identifier: CC BY-NC-SA-4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
 * Author: RHLamb (W3TOY) wwvb@xtcn.com
 *
 * The above copyright notice shall be included in all copies or substantial portions of this software.
 */
static uint8_t *adcbuf=NULL;
static int adcdisp=0,adcslo=0;
static atomic_int adcdone=0;
// static int adcdrop=0,adcchan=0,adcunk=0,adctmo=0;  // for local ADC
static int adcreadtaskrun=1; // 0=exit 1=run 2=idle -1=finished
static uint8_t *adccurprocbuf=NULL;
//static int adc_timedel=0;

#define MY_SAMPLE_RATE 400
#define MY_SYMBOL_PERIOD 1.0f  // 1-bps
static int block_size=(int)((float)MY_SYMBOL_PERIOD*(float)MY_SAMPLE_RATE);
#define MY_XYZMULT 8 // get to I2S minimum speed - decimate later
#define MY_I2S_SAMPLE_RATE (MY_SAMPLE_RATE*MY_XYZMULT)

#include "freertos/queue.h"
#ifdef CONFIG_IDF_TARGET_ESP32S2
#define I2S_MCLK_PIN I2S_GPIO_UNUSED  // 7
#define I2S_BCLK_PIN 6
#define I2S_WS_PIN   5
#define I2S_DOUT_PIN 4 // from ADC confusing marking
#define I2S_DIN_PIN  3 // to ADC confusing marking
#endif
#ifdef CONFIG_IDF_TARGET_ESP32S3
#define I2S_MCLK_PIN I2S_GPIO_UNUSED // 38
#define I2S_BCLK_PIN 42
#define I2S_WS_PIN   18
#define I2S_DOUT_PIN I2S_GPIO_UNUSED
#define I2S_DIN_PIN  17
#endif

#include "driver/i2s_std.h"
static uint32_t i2srxo=0,i2stxu=0;
static size_t i2srxesz=0,i2stxesz=0;
static IRAM_ATTR bool i2s_rx_queue_overflow_callback(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx)
{
  i2srxo++;
  i2srxesz = event->size;
  return false;
}
static IRAM_ATTR bool i2s_tx_queue_underflow_callback(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx)
{
  i2stxu++;
  i2stxesz = event->size;
  return false;
}
static i2s_chan_handle_t rx_chan,tx_chan;  
static int myi2sinit(void)
{
  static uint8_t init=1;
  if(init == 0) return 0;  
  esp_err_t ret;

  //Configure I2S_NUM_0 as Full-Duplex mode
  // Note: in SLAVE mode so BCLK is set by si5351
  i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO,I2S_ROLE_SLAVE);
  //chan_cfg.auto_clear_after_cb = true;
  //chan_cfg.auto_clear_before_cb = true;
  chan_cfg.auto_clear = true;
  
  ret = i2s_new_channel(&chan_cfg,&tx_chan,&rx_chan);
  //ret = i2s_new_channel(&chan_cfg,NULL,&rx_chan);
  if(ret != ESP_OK) {
    myDprintf("Failed to create new channels\n");
    return ret;
  }
  
  i2s_std_config_t std_cfg = {
    .clk_cfg  = I2S_STD_CLK_DEFAULT_CONFIG(MY_I2S_SAMPLE_RATE),
    .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT,I2S_SLOT_MODE_STEREO),
    .gpio_cfg = {
      .mclk = I2S_GPIO_UNUSED,
      .bclk = I2S_BCLK_PIN,
      .ws   = I2S_WS_PIN,
      .dout = I2S_DOUT_PIN,
      .din  = I2S_DIN_PIN,
      .invert_flags = {
        .mclk_inv = false,
        .bclk_inv = false,
        .ws_inv   = false,
      },
    },
  };

  //std_cfg.clk_cfg.clk_src = I2S_CLK_SRC_APLL; // Use APLL for esp32s2
  std_cfg.clk_cfg.clk_src = I2S_CLK_SRC_DEFAULT; // I2S_CLK_SRC_PLL_160M

  myDprintf("i2s_channel_init_std_mode tx_chan\n");
  
  ret = i2s_channel_init_std_mode(tx_chan,&std_cfg);
  if(ret != ESP_OK)return ret;
  i2s_event_callbacks_t txcbs = {
    .on_recv = NULL,
    .on_recv_q_ovf = NULL,
    .on_sent = NULL,
    .on_send_q_ovf = i2s_tx_queue_underflow_callback,
  };

  // default
  //myi2sinit: sample_rate_hz=3200 multiple=256 bclkdiv=1 ext_clk_freq_hz=0
  //myi2sinit: dwidth=32 swidth=0 mode=2 mask=3 ws_width=32
  // test
  //myi2sinit: sample_rate_hz=3200 multiple=128 bclkdiv=1 ext_clk_freq_hz=819200
  //myi2sinit: dwidth=32 swidth=0 mode=2 mask=3 ws_width=32
#ifdef CONFIG_IDF_TARGET_ESP32S2
    myDprintf("sample_rate_hz=%d multiple=%d bclkdiv=%d ext_clk_freq_hz=%d\n",
            std_cfg.clk_cfg.sample_rate_hz,
            std_cfg.clk_cfg.mclk_multiple,
            std_cfg.clk_cfg.bclk_div,
            -1);
#else // CONFIG_IDF_TARGET_ESP32S2  
  myDprintf("sample_rate_hz=%d multiple=%d bclkdiv=%d ext_clk_freq_hz=%d\n",
            std_cfg.clk_cfg.sample_rate_hz,
            std_cfg.clk_cfg.mclk_multiple,
            std_cfg.clk_cfg.bclk_div,
            std_cfg.clk_cfg.ext_clk_freq_hz);
#endif // CONFIG_IDF_TARGET_ESP32S2  
  myDprintf("dwidth=%d swidth=%d mode=%d mask=%d ws_width=%d\n",
	    std_cfg.slot_cfg.data_bit_width,
	    std_cfg.slot_cfg.slot_bit_width,
	    std_cfg.slot_cfg.slot_mode,
	    std_cfg.slot_cfg.slot_mask,
	    std_cfg.slot_cfg.ws_width);
  
  ret = i2s_channel_register_event_callback(tx_chan,&txcbs,NULL);
  if(ret != ESP_OK) return ret;

  myDprintf("i2s_channel_init_std_mode rx_chan\n");
  
  ret = i2s_channel_init_std_mode(rx_chan,&std_cfg);
  if(ret != ESP_OK) return ret;
  i2s_event_callbacks_t rxcbs = {
    .on_recv = NULL,
    .on_recv_q_ovf = i2s_rx_queue_overflow_callback,
    .on_sent = NULL,
    .on_send_q_ovf = NULL,
  };
  ret = i2s_channel_register_event_callback(rx_chan,&rxcbs,NULL);
  if(ret != ESP_OK)return ret;

  myDprintf("i2s_channel_enable tx_chan\n");
  
  ret = i2s_channel_enable(tx_chan);
  if(ret != ESP_OK) return ret;

  myDprintf("i2s_channel_enable rx_chan\n");
  
  ret = i2s_channel_enable(rx_chan);
  if(ret != ESP_OK) return ret;

#ifdef FOOP
  i2s_chan_info_t xinf;
  uint32_t rxb,txb;
  i2s_channel_get_info(rx_chan,&xinf);
  rxb = xinf.total_dma_buf_size;
  i2s_channel_get_info(tx_chan,&xinf);
  txb = xinf.total_dma_buf_size;
  myprintf("total_dma_buf_size = rx:%u tx:%u\n",rxb,txb);
#endif // FOOP
  
  init = 0;  
  return 0;
}
static int aicbufsz;
static int64_t adcbufdelay=0;
static void adcreadtask(void) // read aic320 adc
{
  mysetupwtchdog(120);
  
  uint32_t tot=0;
  size_t n;
  uint8_t *p,*cbuf,*adcrx1,*adcrx2;
  //aicbufsz = block_size*sizeof(int32_t)*2; // done earlier
  adcrx1 = mymalloc(aicbufsz);
  adcrx2 = mymalloc(aicbufsz);
  p = cbuf = adcrx1;
  adcreadtaskrun = 1;

  while(adcreadtaskrun > 0) {
    if(adcreadtaskrun == 2) { mysleep(1); continue; } // IDLE
    mywatchdogyield();

    if(adccurprocbuf == p) adcslo++;
      
    //mytimestamp adc_start_b = mygetts();

    n = 0;
    i2s_channel_read(rx_chan,p,aicbufsz,&n,10000);
    tot = n;
    
    //adc_timedel = (int)(mygetts() - adc_start_b);
    
    adcbuf = cbuf;
    adcdone = tot;

    //myprintf("%s:adcdone=%d\n",__func__,adcdone);
    
    if(cbuf == adcrx1) cbuf = adcrx2; else cbuf = adcrx1;
    tot = 0;
    p = cbuf;    
  }
  adcdone = 0;
  myfree(adcrx1); adcrx1 = NULL;
  myfree(adcrx2); adcrx2 = NULL;
  adcreadtaskrun = -1;
  esp_task_wdt_delete(NULL);
  vTaskDelete(xTaskGetCurrentTaskHandle());
}
static void myadc_init(void)
{
  static uint8_t init=1;
  if(init == 0) return;
  aicbufsz = block_size*sizeof(int32_t)*2;
  myDprintf("Setup I2S and AIC3254 sample rate=%dHz bufsz=%d\n",MY_I2S_SAMPLE_RATE,aicbufsz);
  // (aicbufsz/2)/MY_I2S_SAMPLE_RATE /2 because 2 samples/adc
  // eg.:rate=3200Hz bufsz=3200->adcbufdelay=0.5sec
  //cur_fs_freq = 256.0*MY_I2S_SAMPLE_RATE; // need to sync w/ other code
  adcbufdelay = (int64_t)aicbufsz;
  adcbufdelay = (adcbufdelay*1000000)/(2*MY_I2S_SAMPLE_RATE);

  myi2sinit();  
  aic_init();  
  mydelay(100);
  myDprintf("Start adc task\n");
  xTaskCreatePinnedToCore((TaskFunction_t)adcreadtask,"adcreadtask",
              4096/sizeof(configSTACK_DEPTH_TYPE),
              NULL,(tskIDLE_PRIORITY+1),NULL,
			  0); // use CPU0 since LCD uses CPU1
  mydelay(100);
  myDprintf("done start\n");
  init = 0;
}
static void adcstats(void)
{
  myprintf("dspdelay=%ldus errs slo:%d i2srxo/esz=%d/%lu i2stxu/esz=%d/%lu\n",
	   (int32_t)adcbufdelay,adcslo,i2srxo,i2srxesz,i2stxu,i2stxesz);
  i2srxo = 0;
  i2srxesz = 0;
  i2stxu = 0;
  i2stxesz = 0;
}

#define LEDC_TIMER_1_FREQ 5000 // shared among LEDC_CHANNEL_*
#ifdef CONFIG_IDF_TARGET_ESP32S3
#define MY_PWM_PIN 38 // I2S_MCLK_PIN
#endif
#ifdef CONFIG_IDF_TARGET_ESP32S2
#define MY_PWM_PIN 11
#endif
static void pwm_dac(float val)
{
  static uint8_t init=1;
  if(init) {
    init = 0;
    const ledc_channel_config_t chn = {
      .gpio_num = (gpio_num_t)MY_PWM_PIN,
      .speed_mode = LEDC_LOW_SPEED_MODE,
      .channel = LEDC_CHANNEL_1, // LEDC_CHANNEL_0 used by disp
      .intr_type = LEDC_INTR_DISABLE,
      .timer_sel = LEDC_TIMER_1,
      .duty = 0,
      .hpoint = 0,
      .flags = {
	.output_invert = 0
      }
    };
    const ledc_timer_config_t tmr = {
      .speed_mode = LEDC_LOW_SPEED_MODE,
      .duty_resolution = LEDC_TIMER_10_BIT,
      .timer_num = LEDC_TIMER_1,
      .freq_hz = LEDC_TIMER_1_FREQ, // watch other timer_1 use
      .clk_cfg = LEDC_AUTO_CLK
    };
    ESP_ERROR_CHECK(ledc_timer_config(&tmr));
    ESP_ERROR_CHECK(ledc_channel_config(&chn));
  }
  if(val > 1) val = 1; else if(val < -1) val = -1; 
  uint32_t dt=(uint32_t)(1023.0*(val+1)/2.0);
  ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_1,dt));
  ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_1));
}

static int dotest(int argc,char *argv[])
{
  adcdisp = 1; // show adc stats
  
  char out[132];
  wwvbhourly(out);
  myprintf("%s\n",out);
  if(dodiskstuff == NULL) {
    if(argc > 1) {
      if(strcmp(argv[1],"log") == 0) {
	dodiskstuff = mystrdup("log");
	return 0;
      }
      if(strcmp(argv[1],"update") == 0) {
        dodiskstuff = mystrdup("update");
        return 0;
      }
      myprintf("unknown |%s|\n",argv[1]);
      return 0;
    }
  }
  return 0;
}


/* 
 * WWVB processing 
 *
 * Copyright notice:
 * SPDX-FileCopyrightText: Copyright (c) 2025-2026 Richard Lamb
 * SPDX-License-Identifier: CC BY-NC-SA-4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
 * Author: RHLamb (W3TOY) wwvb@xtcn.com
 *
 * The above copyright notice shall be included in all copies or substantial portions of this software.
 *
 * https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=914904
 * From scratch RHLamb 2025
 * Systematic hamming(31,26) with WWVB parity check selections. 
 * Tested for all 26bit numbers.
 * Hamming codes can detect one-bit and two-bit errors,
 * or correct one-bit errors without detection of uncorrected errors. 
 *
 * Notes on other parts:
 *  1. sync clocks... done
 *  2. sync on sync word only even if rest does not ecc to keep time tracking.
 *  show us level diff (mod 60s) when sync word detected to show accuracy.
 *  3. Predict full 60 bits for each min, then show which ones dont match
 *  and maybe rate/accept/correct.
 * 5. fix sync approach to sync then read. not the other way
 */
#include <complex.h>
#include <math.h>

static uint32_t wwvbparity(uint32_t wwt)
{
  /*
  int tp0[]={23, 21, 20, 17, 16, 15, 14, 13, 9, 8, 6, 5, 4, 2, 0,-1};  
  int tp1[]={24, 22, 21, 18, 17, 16, 15, 14, 10, 9, 7, 6, 5, 3, 1,-1};  
  int tp2[]={25, 23, 22, 19, 18, 17, 16, 15, 11, 10, 8, 7,6, 4, 2,-1};  
  int tp3[]={24, 21, 19, 18, 15, 14, 13, 12, 11, 7, 6, 4, 3,2,0,-1};  
  int tp4[]={25, 22, 20, 19, 16, 15, 14, 13, 12, 8, 7, 5, 4, 3, 1,-1};
  //        22  2222 1111  1111 11
  //        54  3210 9876  5432 1098  7654 3210
  // 0000 0000  0000 0000  0000 0000  0000 0000
  //      0000  1011 0011  1110 0011  0111 0101 0x00B3E375
  //      0001  0110 0111  1100 0110  1110 1010 0x0167C6EA
  //      0010  1100 1111  1000 1101  1101 0100 0x02CF8DD4
  //      0001  0010 1100  1111 1000  1101 1101 0x012CF8DD
  //      0010  0101 1001  1111 0001  1011 1010 0x0259F1BA
H parity check Matrix
25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 p0 p1 p2 p3 p4

0   0  1  0  1  1  0  0  1  1  1  1  1  0  0  0 1 1 0 1 1 1 0 1 0 1  1  0  0  0  0
0   1  0  1  1  0  0  1  1  1  1  1  0  0  0  1 1 0 1 1 1 0 1 0 1 0  0  1  0  0  0
1   0  1  1  0  0  1  1  1  1  1  0  0  0  1  1 0 1 1 1 0 1 0 1 0 0  0  0  1  0  0
0   1  0  0  1  0  1  1  0  0  1  1  1  1  1  0 0 0 1 1 0 1 1 1 0 1  0  0  0  1  0
1   0  0  1  0  1  1  0  0  1  1  1  1  1  0  0 0 1 1 0 1 1 1 0 1 0  0  0  0  0  1

14  A  5 16  B 11 1C  E  7 17 1F 1B 19 18  C  6 3151E F131D1A D12 9  1  2  4  8 10
  */  
  uint32_t x,y=0;
  uint8_t b;

  x = wwt&0x0259F1BA;
  b = 0;
  while(x) { if((x&1)) b++; x >>= 1; }
  y <<= 1;
  if(b&1) y |= 1;

  x = wwt&0x012CF8DD;
  b = 0;
  while(x) { if((x&1)) b++; x >>= 1; }
  y <<= 1;
  if(b&1) y |= 1;

  x = wwt&0x02CF8DD4;
  b = 0;
  while(x) { if((x&1)) b++; x >>= 1; }
  y <<= 1;
  if(b&1) y |= 1;

  x = wwt&0x0167C6EA;
  b = 0;
  while(x) { if((x&1)) b++; x >>= 1; }
  y <<= 1;
  if(b&1) y |= 1;

  x = wwt&0x00B3E375;
  b = 0;
  while(x) { if((x&1)) b++; x >>= 1; }
  y <<= 1;
  if(b&1) y |= 1;

  return y;
}
static int wwvbcheck(uint32_t *wwtp,uint32_t ecc)
{
  int ret=0;
  int remap[]={-1,-1,9,-1,23,10,17,-1,0,24,21,11,2,18,6,-1,20,1,5,25,8,22,16,12,
13,3,14,19,4,7,15};
  uint32_t wwt=*wwtp;
  uint8_t cd=0;
 retry:
  uint8_t y = (uint8_t)(wwvbparity(wwt)&0x1F); // 5 bit
  int parity = (y^(uint8_t)ecc)&0x1F;
  if(parity == 0) myNprintf(" ecc/");
  else {
    ret = 1;
    parity -= 1; // correct for index
    int idx = remap[parity];
    //myNprintf(" %02X/%02X/%d/%d",y,(uint8_t)ecc,parity,idx);
    myNprintf(" x=%d",idx);
    if(cd == 0 && idx >= 0) {
      wwt ^= (0x00000001<<idx);      
      y = 0;
      cd = 1;
      goto retry;
    }
    myNprintf(" error");
    return -1;
  }
  *wwtp = wwt;
  return ret;
}
static atomic_int_fast64_t wwvbbpsksec; //static time_t  wwvbbpsksec;
static atomic_int_fast64_t wwvbbpskok; //static int64_t wwvbbpskok; 
static uint8_t wwvbbpskdstls,wwvbbpskndst,wwvbbpsknotice=0;
static uint8_t wwvbbpskdstls0=0,wwvbbpskndst0=0;
static uint8_t gotgoodone=0; // fixsync
static int wwvbdec(bool *mb,int mbn,int flg)
{
  int k,j,n=0;
  uint32_t x,ecc,tmins;
  char out[256];

  memset(out,0,sizeof(out));
  // show bits
  j = mbn;
  for(k=0;k<46;k++) { // 60 for all, 47 to skip sync
    if(k == 5 || k == 16 || k == 26 || k == 34 || k == 40 || k == 46) out[n++] = ' ';
    if(flg) out[n++] = mb[j]?'0':'1';
    else out[n++] = mb[j]?'1':'0';
    j = (j + 1)%60;
  }
  out[n] = '\0';
  myNprintf("%s",out);
	    
  j = mbn;
  // ecc bits
  x = 0;
  for(k=0;k<5;k++) {
    x <<= 1;
    if(flg) {
      if(!mb[j]) x |= 1;
    } else {
      if(mb[j]) x |= 1;
    }
    j = (j + 1)%60;
  }
  ecc = x; // Hamming (31,26)
  
  // time bits
  x = 0;
  for(k=0;k<29;k++) {
    if(k != 1 && k != 11 && k != 21) {
      x <<= 1;
      if(flg) {
        if(!mb[j]) x |= 1;
      } else {
        if(mb[j]) x |= 1;
      }
    }
    j = (j + 1)%60;
  }
  tmins = x; // min of this century  3FFFFFF is bad (max 26bit)

  // dst bits
  x = 0;
  for(k=0;k<6;k++) {
    if(k == 2) {
      if(mb[j]) wwvbbpsknotice = 1; else wwvbbpsknotice = 0; 
    } else {
      x <<= 1;
      if(flg) {
	if(!mb[j]) x |= 1;
      } else {
	if(mb[j]) x |= 1;
      }
    }
    j = (j + 1)%60;
  }
  wwvbbpskdstls = (uint8_t)x; // dst/leap sec warning

  // next dst
  x = 0;
  for(k=0;k<6;k++) {
    x <<= 1;
    if(flg) {
      if(!mb[j]) x |= 1;
    } else {
      if(mb[j]) x |= 1;
    }
    j = (j + 1)%60;
  }
  wwvbbpskndst = (uint8_t)x; // next dst schedule
  
  // decode, fix time
  if(wwvbcheck(&tmins,ecc) < 0) { // try to fix errors
    myNprintf("%s xchk\n",flg?"-":"+");
    return 0;
  }  
  time_t t=(time_t)tmins;
  t *= 60;
  t += 946684800; // jan 1 2000
  if(t > 4102444800) { // jan 1 2100
    myNprintf("%s range1\n",flg?"-":"+");
    return 0;
  }
  if(curtime0) { // if I read this from config
    if(labs((long)(curtime0 - t)) > (86400*365*10)) { // too far off from reality
      myNprintf("%s range2\n",flg?"-":"+");
      return 0;
    }
  }
  
  // Also check if in right century.  I got 3 2099 and it  passed
  // showwwvb() must accept new 3X readings to reset a bad initial setting.
  
  // timestamp correct time received. wwvb:-143 ntp:-598,263us gps:-521,975us
  wwvbbpskok = esp_timer_get_time(); // to correct for processing delay
  wwvbbpskok -= adcbufdelay; // ADC buffer reciveing delay + DSP delay
  wwvbbpsksec = t + 60 + 13; // frame time is for last min plus 13 sync frame bits
  
  gotgoodone = 63; // fixsync
  myNprintf("%s %s",flg?"-":"+",ctime(&t));
  //myNprintf(" %lX\n",t);
  return 0;
}

// fixsync instead of looking for post 0+SYNC then looking back (loses 9 and 39 min)
// see SYNC, then capture next 47 bits, then wwvbdec()
// time frame: 0 0011101101000
// msg frame:  0 1101000111010
/*
 * start of 360sec ext frame at HH:10 and HH:40 seq=1-124 dep on HH
   uint8_t ilfsr=0x80-seq; // seq1=0x7F                                        
   myNprintf("%02d 0+",seq);
   for(i=0;i<127;i++) {
     myNprintf("%s",ilfsr&0x40?"1":"0");
     bool s = (ilfsr&0x40?1:0)^(ilfsr&0x20?1:0)^(ilfsr&0x10?1:0)^(ilfsr&0x02?1:0);
     ilfsr <<= 1;
     ilfsr |= s;
   }
   myNprintf("\n");
 *
 */
#define NSYNCBITS 14
static bool syncbits[]={0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0};
static bool msyncbits[]={0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0};
static bool wvsyncx[14];
static int syncxi=0;
static bool savebits[60];
static int savebitsn=0;
static int bitdex=0;
static int procbit(bool lastb)
{
  int k,j;
  
  wvsyncx[syncxi] = lastb;
  syncxi = (syncxi + 1)%14;
  
  savebits[savebitsn] = lastb;
  savebitsn = (savebitsn + 1)%60;

  // try to predict sync bits
  bitdex++;
  if(bitdex == 60) { // 60 or 61 sync should be here.
    char out[16];
    //myNprintf("maybe sync:");
    j = syncxi;
    for(k=0;k<14;k++) {
      //myNprintf("%s",wvsyncx[j]?"1":"0");
      out[k] = wvsyncx[j]?'1':'0';
      j = (j + 1)%14;
    }
    out[k] = '\0';
    myNprintf("maybe sync: %s\n",out);
  }
  
  j = syncxi;
  for(k=0;k<14;k++) {     
    if(wvsyncx[j] != syncbits[k]) break;
    j = (j + 1)%14;
  }
  if(k == 14) {
    myNprintf("Sync found!! %d\n",bitdex);
    bitdex = 0;
    gotgoodone = 0; // fixsync
    wwvbdec(savebits,savebitsn,0);
    return 0;
  }

  j = syncxi;
  for(k=0;k<14;k++) {
    if(wvsyncx[j] == syncbits[k]) break;
    j = (j + 1)%14;
  }
  if(k == 14) {
    myNprintf("Sync INV found!! %d\n",bitdex);
    bitdex = 0;
    gotgoodone = 0; // fixsync
    wwvbdec(savebits,savebitsn,1);
    return 0;
  }

  
  // tbd: message frame sync
  j = syncxi;
  for(k=0;k<14;k++) {
    if(wvsyncx[j] != msyncbits[k]) break;
    j = (j + 1)%14;
  }
  if(k == 14) {
    myNprintf("Mync found!! %d\n",bitdex); // at 187 and 6
    //bitdex = 0;
    //gotgoodone = 0; // fixsync
    //wwvbmdec(savebits,savebitsn,0);
    return 0;
  }

  j = syncxi;
  for(k=0;k<14;k++) {
    if(wvsyncx[j] == msyncbits[k]) break;
    j = (j + 1)%14;
  }
  if(k == 14) {
    myNprintf("Mync INV found!! %d\n",bitdex); // at 187 and 6
    //bitdex = 0;
    //gotgoodone = 0; // fixsync
    //wwvbmdec(savebits,savebitsn,1);
    return 0;
  }

  return 0;
}

/*
thinkin:match all shifts? parralel matched filter?
cnt    totsum,savebits[],wvsyncx[]
cnt+1  totsum1
..
cnt+99 totsum99

60*100 = 6000 bits. then slide a 14*100 bit window to find best sync word
 */
static uint8_t ext_led_state=0;
static float ext_agc_gain=0.0f,ext_cot_err=0.0f,ext_last_bit=0.0f;
static float ext_signal=0.0f,ext_noise=0.0f;
static float ext_cot_freq=0.0f,ext_cot_perr=0.0f;
static float vcxovolt=0;
static bool ext_cot_ext=false;
#define CLKTRANSMAG -30 // -30
#define CLKTRANSTOT  0 // 0
static float dclkerr=0; // avg data clock error
static int procsignal(float tot)
{
  static int cnt=0;
  static float totsum=0.0f,clktoggle=-1;
  static float myavgtc=1e-2;
  // 100 samples/s
  cnt = (cnt + 1)%100;

  if(cnt == 0 || (tot > CLKTRANSTOT && totsum < CLKTRANSMAG)) {    
    // bad: err=0.254936 snr=0.749848 xyz err=0 snr=0 also bad
    // good:err=0.042552 snr=1.084074 xyz   measured snr=+10db
    if(cnt && gotgoodone == 0) { // 1bps data clock "PLL"
      if(cnt < 50) { // 1-49 transition happened after clk
	//cnt--; // slow down
	
	// instead of cnt maybe use us timing to get finner grain avg estimate of data clock error
	// re:costas loop: feed vcxo d_error directly and monitor on dvm
	
	dclkerr = (dclkerr*(1.0 - myavgtc)) - (myavgtc*cnt);
      } else { // 50-99 transition happened before clk
	//cnt++;
	dclkerr = (dclkerr*(1.0 - myavgtc)) + (myavgtc*(100-cnt));
      }
      cnt = 0;
      time_t _tt=curtime;
      myprintf("%08x %+7.2f pe=%+.3f/%.3f snr=%.2f agc=1/%.0f f=%+.2fhz cke=%+.3f sync\n",
	       (uint32_t)_tt,totsum,ext_cot_perr,ext_cot_err,
	       ext_signal/ext_noise,1/ext_agc_gain,
	       ext_cot_freq*400/(2*M_PI),dclkerr);
    } else {
      dclkerr = (dclkerr*(1.0 - myavgtc)); // cnt == 0
      // ext_cot_perr=avg costas phase error
      // ext_cot_err=avg costas phase error magnitude
      // dclkerr=avg data clock error
      // was printf() for speed.
      time_t _tt=curtime;
      myprintf("%08x %+7.2f pe=%+.3f/%.3f snr=%.2f agc=1/%.0f f=%+.2fhz cke=%+.3f vco:%+.3f%s\n",
	       (uint32_t)_tt,totsum,ext_cot_perr,ext_cot_err,
	       ext_signal/ext_noise,1/ext_agc_gain,
	       ext_cot_freq*400/(2*M_PI),
	       dclkerr,vcxovolt,ext_cot_ext?"E":""); // dunno why *400
      // LO info: "%.2f%+.2fhz",cur_lo_freq,ext_cot_freq*400/(2*M_PI)
#ifdef FOOP
      char out[256];
      sprintf(out,"%08lx %+7.2f",(uint32_t)_tt,totsum);
      sprintf(&out[strlen(out)]," pe=%+.3f/%.3f",ext_cot_perr,ext_cot_err);
      sprintf(&out[strlen(out)]," snr=%.2f agc=1/%.0f",ext_signal/ext_noise,1/ext_agc_gain);
      sprintf(&out[strlen(out)]," f=%+.2fhz cke=%+.3f",ext_cot_freq*400/(2*M_PI),dclkerr);
      sprintf(&out[strlen(out)]," vco:%+.3f%s",vcxovolt,ext_cot_ext?"E":"");
      myprintf("%s\n",out);
#endif // FOOP
    }
    ext_last_bit = totsum;
    
    if(totsum > 0) {
      procbit(true);
      ext_led_state = 1;
    } else {
      procbit(false);
      ext_led_state = 0;
    }
    totsum = 0;
    clktoggle = -clktoggle;    
  }
  
  totsum += tot;
  
  return 0;
}


/*
 * DSP stuff based on GNU Radio
 *copyright*
 * Copyright 2005,2010,2013 Free Software Foundation, Inc.
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 * Modifications:
 * SPDX-FileCopyrightText: Copyright (c) 2025-2026 Richard Lamb
 * SPDX-License-Identifier: CC BY-NC-SA-4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ 
 * Author: RHLamb (W3TOY) wwvb@xtcn.com
 *
 * The above copyright notice shall be included in all copies or substantial portions of this software.
 */
#define MY_CENTER_FREQ 100
#define MY_BASEBAND_RATE 100
#define MY_DECIMATION (MY_SAMPLE_RATE/MY_BASEBAND_RATE)
#define M_PI 3.14159265358979323846

static inline void dotprod_realtaps(float complex *result,const float complex *in,const float* taps,int n)
{
  float r[2];
  float *rp = &r[0],*ip = &r[1];
  const float *a = (float*)in;
  const float *b = taps;
  *rp = 0;
  *ip = 0;
  for(int i=0;i<n;i++) {
    *rp += ((*a++) * (*b));
    *ip += ((*a++) * (*b++));
  }
  *result = *(float complex *)(&r[0]);
}

#define MYNTAPS 257  // must be odd
static float taps[MYNTAPS];

// From GNU Radio
static int root_raised_cosine(double gain,
                       double sampling_freq,
                       double symbol_rate, // Symbol rate, NOT bitrate (unless BPSK)
                       double alpha,       // Excess Bandwidth Factor
                       int ntaps)
{
  if((ntaps&1) == 0) return -1; // ensure that ntaps is odd
  double spb = sampling_freq / symbol_rate; // samples per bit/symbol
  double scale = 0;  
  for(int i = 0; i < ntaps; i++) {
    double x1, x2, x3, num, den;
    double xindx = i - ntaps / 2;
    
    x1 = M_PI * xindx / spb;
    x2 = 4 * alpha * xindx / spb;
    x3 = x2 * x2 - 1;
    
    if(fabs(x3) >= 0.000001) { // Avoid Rounding errors...
      if(i != ntaps / 2)
        num = cos((1 + alpha) * x1) + sin((1 - alpha) * x1) / (4 * alpha * xindx / spb);
      else
        num = cos((1 + alpha) * x1) + (1 - alpha) * M_PI / (4 * alpha);
      
      den = x3 * M_PI;
    } else {
      if(alpha == 1) {
        taps[i] = -1;
        scale += taps[i];
        continue;
      }
      x3 = (1 - alpha) * x1;
      x2 = (1 + alpha) * x1;
      num = (sin(x2) * (1 + alpha) * M_PI -
             cos(x3) * ((1 - alpha) * M_PI * spb) / (4 * alpha * xindx) +
             sin(x3) * spb * spb / (4 * alpha * xindx * xindx));
      den = -32 * M_PI * alpha * alpha * xindx / spb;
    }
    taps[i] = 4 * alpha * num / den;
    scale += taps[i];
  }
  for (int i = 0; i < ntaps; i++) taps[i] = taps[i] * gain / scale;
  return 0;
}

static int wwvbinitialized=0; // for UI
static void wwvbtask(void)
{
  /* DSP stuff */
  int i,dj;  
  int samp_rate_2=MY_SAMPLE_RATE,samp_rate_3=MY_CENTER_FREQ;
  int fst=samp_rate_2/samp_rate_3;
  double gain=(double)fst;

  if(root_raised_cosine(gain,samp_rate_2,6,0.35,MYNTAPS)) {
    myDprintf("root_raised_cosine tap calc failed\n");
    return;
  }
  
  // AGC
  float a_rate = 1e-4;
  float a_reference = 1.0;
  float a_gain = 1.0;
  float a_max_gain = 0.0;
  
  // COSTAS
  float c_phase=0,c_freq=0,d_error=0;
  float d_max_freq=1, d_min_freq=-1;
  float d_damping=sqrtf(2.0f)/2.0f, d_loop_bw=0.00628;
  float d_alpha,d_beta;  
  float denom = (1.0 + 2.0 * d_damping * d_loop_bw + d_loop_bw * d_loop_bw);
  d_alpha = (4 * d_damping * d_loop_bw) / denom; // 0.017605
  d_beta = (4 * d_loop_bw * d_loop_bw) / denom; // 0.000156
  
  // XLATE BPF
  float d_center_freq=MY_CENTER_FREQ;
  float d_sampling_freq=MY_SAMPLE_RATE;
  
  float complex d_phase=1;
  float complex d_phase_incr=1;
  
  float complex phase = d_phase;
  phase /= cabsf(phase);
  //float delta_freq = d_center_freq - d_prev_center_freq;
  float delta_freq = d_center_freq;
  float delta_omega = 2.0 * M_PI * delta_freq / d_sampling_freq;
  float delta_phase = -delta_omega * (MYNTAPS - 1) / 2.0;

  phase *= cexpf( (0 + delta_phase*I) );
  d_phase = phase / cabsf(phase);
  
  // x(t) -> BPF -> decim -> (mult by fwT0*decim) -> y(t)
  // The BPF is the baseband filter (LPF) moved up to the
  // center frequency fwT0. We then apply a derotator
  // with -fwT0 to downshift the signal to baseband.

  i = MYNTAPS*sizeof(float complex);
  float complex *ctaps=(float complex *)mymalloc(i);
  
  float fwT0 = 2 * M_PI * d_center_freq / d_sampling_freq;
  for(i=0;i<MYNTAPS;i++) {
    ctaps[i] = cexpf( (0 + (i*fwT0)*I) )*taps[i];
  }

  for(int j=0;j<MYNTAPS/2;j++) {
    float complex tcx = ctaps[j];
    ctaps[j] = ctaps[MYNTAPS-j-1];
    ctaps[MYNTAPS-j-1] = tcx;
  }
  
  float complex incr=cexpf(0 - (fwT0*MY_DECIMATION)*I);
  d_phase_incr = incr/cabsf(incr);

  // memset(z,0,MYNTAPS*sizeof(float)) might not work for float
  float *z=(float *)mymalloc(MYNTAPS*sizeof(float));
  for(int j=0;j<MYNTAPS;j++) z[j] = 0.0f; // init to zero. memset is not same

  int nout = 1;
  float complex cout[nout+1];
  dj = 0;
  /* end DSP stuff */

  mysetupwtchdog(120);
  myadc_init();

  wwvbinitialized = 1; // let UI now it ok

  size_t ns;
  int n=0;
  uint8_t *qa;
  
  while(1) {
    if(adcreadtaskrun == 1 && adcdone) {
      qa = adcbuf;
      int na=adcdone;
      float qaf=0;
      float fsamp=0;
      adcdone = 0;
      adccurprocbuf = adcbuf;  // indicate current buffer we are processing
      int js=0; // for averaging oversampling
      for(i=0;i<na;i+=8) { // 32-bit/channel, 2 channels
	
	js = (js+1)%MY_XYZMULT;
	
	if(js == 0) {
	  int32_t ya = *(int32_t *)&qa[i+4]; // qa[i] = IN1_L, qa[i+4] = IN1_R
	  qaf = (float)ya;
	  fsamp = qaf/0x1000000; // (64000.0/1.5);

        /* start DSP stuff  */
        ns = 1;
        while(ns-- > 0) {
          z[n] = fsamp;
          n = (n+1)%MYNTAPS;

          // FILTER
          if( (dj++ %4) ) continue;
          
          float complex *xyt=ctaps;
          dotprod_realtaps(cout,xyt,&z[n],MYNTAPS-n);
          float complex y0=cout[0];
          dotprod_realtaps(cout,&xyt[MYNTAPS-n],z,n);
          cout[0] += y0;
	  
          // ROTATE - DO WE NEED THIS? doesnt seem to change output
          cout[0] *= d_phase;
          d_phase *= d_phase_incr;
	  	   
          // AGC
          cout[0] *= a_gain;
          a_gain += a_rate*(a_reference - sqrtf(crealf(cout[0])*crealf(cout[0]) + cimagf(cout[0])*cimagf(cout[0])) );
          if(a_max_gain > 0.0 && a_gain > a_max_gain) a_gain = a_max_gain;
	  ext_agc_gain = a_gain; // for display min=typ:0.005 int0 mix:-40dbm floor, +5db SNR
	  if(ext_agc_gain == 0) ext_agc_gain = 0.0001;
	  
          // COSTAS
          const float complex nco_out = cosf(-c_phase) + sinf(-c_phase)*I;
          cout[0] *= nco_out;
          d_error = crealf(cout[0])*cimagf(cout[0]); // bpsk
          d_error = 0.5*( fabsf(d_error+1.0f) - fabsf(d_error-1.0f) ); // limit<+/-1
#define DO_VCXO
#ifdef DO_VCXO
	  // How do I lock an external vcxo to this?
	  c_freq = c_freq + d_beta * d_error; // b=0.000156
	  if(c_freq > d_max_freq) c_freq = d_max_freq;
	  else if(c_freq < d_min_freq) c_freq = d_min_freq;
	  // pwm_dac(c_freq*10) can track frequency with d_error low
	  // (d_beta = 0.001) but still doesnt phase lock.
	  // h/w:gpio38 5k pwm mod into 100k+1uf rc lpf to vcxo.
	  // tried pwm_dac(d_error) but pwm_dac(c_freq) works better.
	  // could DAC sin(c_freq+x). DDS? Naw...si5351 random.
	  // how bout: vcxovolt = c_freq + d_alpha*d_error like r-c-r pll filter
	  // this lets instant phase corrections and long term freq correction
	  vcxovolt = vcxovolt + 0.005*c_freq;
	  vcxovolt = (fabsf(vcxovolt+1)-fabsf(vcxovolt-1))/2;
#define FPTHRESHOLD 0 // ((float)(0.10*2*M_PI/400))	  
	  if(fabsf(ext_cot_freq) > FPTHRESHOLD) { // do freq lock. let local loop do phase
	    ext_cot_ext = false;
	    c_phase = c_phase + c_freq + d_alpha * d_error; // a=0.017605
	    while(c_phase > (2 * M_PI)) c_phase -= 2 * M_PI;
	    while(c_phase < (-2 * M_PI)) c_phase += 2 * M_PI;
#ifdef DO_VCXO_FREQ // uses up PIN 38//11
	    pwm_dac(vcxovolt);
#endif // DO_VCXO_FREQ 	    
	  } else { // phase lock external osc.
	    ext_cot_ext = true;
#ifdef DO_VCXO_FREQ // uses up PIN 38
	    pwm_dac(vcxovolt + 0.1*d_error); // vcxovolt + d_alpha*d_error
#endif // DO_VCXO_FREQ	    
	  }
	  //pwm_dac(c_freq*10); // to vcxo
	  //pwm_dac(d_error); // doesnt work. leaves d_error non-zero
#else // DO_VCXO
	  c_freq = c_freq + d_beta * d_error; // b=0.000156
          c_phase = c_phase + c_freq + d_alpha * d_error; // a=0.017605
          while(c_phase > (2 * M_PI)) c_phase -= 2 * M_PI;
          while(c_phase < (-2 * M_PI)) c_phase += 2 * M_PI;
          if(c_freq > d_max_freq) c_freq = d_max_freq;
          else if(c_freq < d_min_freq) c_freq = d_min_freq; //  -1 to + 1 radian
#ifdef FOOP	  
	  vcxovolt = vcxovolt + 0.01*c_freq;
	  vcxovolt = (fabsf(vcxovolt+1)-fabsf(vcxovolt-1))/2;
	  pwm_dac(vcxovolt); // to vcxo to zero out avg c_freq and sync adc mclk
#endif // FOOP	  
	  // h/w:gpio38 5k pwm mod into 100k+2uf rc lpf to 10k-v/2 in to vcxo
	  // p2 = p2 + (2*M_PI*1) + c_freq + (d_alpha*d_error);
	  // cosf(p2) via bclk DAC = 1Hz locked sinewave?
#endif // DO_VCXO
	  
	  // Signal STATS
	  ext_signal = (1-0.001)*ext_signal + 0.001*fabsf(crealf(cout[0]));
	  ext_noise = (1-0.001)*ext_noise + 0.001*fabsf(cimagf(cout[0]));
	  if(ext_noise == 0) ext_noise = 0.001;
	  ext_cot_err = (1-0.001)*ext_cot_err + 0.001*fabsf(d_error);
	  ext_cot_perr = (1-0.001)*ext_cot_perr + 0.001*d_error;
#define MYRC 0.001f	  
	  ext_cot_freq = (1.0-MYRC)*ext_cot_freq + MYRC*c_freq;
	
          // OUTPUT
          procsignal(crealf(cout[0]));
        }
        /* end DSP stuff */
	
      }
    }

    if(adcdisp) {
      adcdisp = 0;
      myprintf("adcdone=%d [0]=%.3fV errs slo:%d\n",na,fsamp,adcslo);
      myprintf("i2srxo/esz=%d/%lu i2stxu/esz=%d/%lu\n",i2srxo,i2srxesz,i2stxu,i2stxesz);
      i2srxo = 0;
      i2srxesz = 0;
      i2stxu = 0;
      i2stxesz = 0;
    }
    if(adcdone || adcbuf != qa) myDprintf("Failed to process data fast enough adcdone=%d\n",adcdone);

    } else {
      
      if(adcreadtaskrun == 2) mysleep(1); else mywatchdogyield();
    
    }
  adccurprocbuf = NULL; // clear indicator
    
  }

}
/*end copyright*/

/*
 * SI5351 stuff
 *copyright*
 * This work is licensed under the Creative Commons Attribution-ShareAlike
 * 4.0 International License. To view a copy of this license, visit
 * http://creativecommons.org/licenses/by-sa/4.0/
 * see silabs AN619
 * PLLA/B, multisynth+R 0/1/2
 */
#define SI5351_BUS_BASE_ADDR 0x60
static i2c_master_dev_handle_t si5351_dev;
//#define XTAL_FREQ 27000000
#define XTAL_FREQ 25000000  // vcxo 
//#define XTAL_FREQ 26000000  // my wwvb board 2ppm tcxo 

enum si5351_clock {SI5351_CLK0, SI5351_CLK1, SI5351_CLK2, SI5351_CLK3,SI5351_CLK4, SI5351_CLK5,SI5351_CLK6, SI5351_CLK7};
enum si5351_drive {SI5351_DRIVE_2MA, SI5351_DRIVE_4MA, SI5351_DRIVE_6MA, SI5351_DRIVE_8MA};
#define SI5351_CRYSTAL_LOAD_0PF         (0<<6)
#define SI5351_CRYSTAL_LOAD_6PF         (1<<6)
#define SI5351_CRYSTAL_LOAD_8PF         (2<<6)
#define SI5351_CRYSTAL_LOAD_10PF        (3<<6)
#define SI5351_OUTPUT_ENABLE_CTRL       3
#define SI5351_CLK0_CTRL                16

#define SI_CLK0_CONTROL 16 // Register definitions
#define SI_CLK1_CONTROL 17
#define SI_CLK2_CONTROL 18
#define SI_SYNTH_PLL_A 26
#define SI_SYNTH_PLL_B 34
#define SI_SYNTH_MS_0 42
#define SI_SYNTH_MS_1 50
#define SI_SYNTH_MS_2 58
#define SI_CLK0_PHOFF 165 // OH2BTG addition for phase offset
#define SI_CLK1_PHOFF 166 // OH2BTG addition for phase offset
#define SI_CLK2_PHOFF 167 // OH2BTG addition for phase offset
#define SI_PLL_RESET 177

// p53 https://www.skyworksinc.com/-/media/Skyworks/SL/documents/public/application-notes/AN619.pdf
#define SI_SPREADSPECTRUM 149 // si5351_write_reg(SI_SPREADSPECTRUM,0x00) &= ~0x80

#define SI_R_DIV_1 0b00000000 // R-division ratio definitions
#define SI_R_DIV_2 0b00010000
#define SI_R_DIV_4 0b00100000
#define SI_R_DIV_8 0b00110000
#define SI_R_DIV_16 0b01000000
#define SI_R_DIV_32 0b01010000
#define SI_R_DIV_64 0b01100000
#define SI_R_DIV_128 0b01110000
#define SI_CLK_SRC_PLL_A 0b00000000
#define SI_CLK_SRC_PLL_B 0b00100000

//#define DO_SI5351IQ

static int si5351_write_reg(uint8_t reg, uint8_t data)
{
  uint8_t ob[8];
  ob[0] = reg;
  ob[1] = data;
  return myi2cwrite(si5351_dev,ob,2);
}
static void si5351PLL(uint8_t pll,uint8_t mult,uint32_t num,uint32_t denom)
{
  uint32_t P1;
  uint32_t P2;
  uint32_t P3;
  
  P1 = (uint32_t)(128 * ((float)num / (float)denom));
  P1 = (uint32_t)(128 * (uint32_t)(mult) + P1 - 512);
  P2 = (uint32_t)(128 * ((float)num / (float)denom));
  P2 = (uint32_t)(128 * num - denom * P2);
  P3 = denom;
  
  si5351_write_reg(pll + 0, (P3 & 0x0000FF00) >> 8);
  si5351_write_reg(pll + 1, (P3 & 0x000000FF));
  si5351_write_reg(pll + 2, (P1 & 0x00030000) >> 16);
  si5351_write_reg(pll + 3, (P1 & 0x0000FF00) >> 8);
  si5351_write_reg(pll + 4, (P1 & 0x000000FF));
  si5351_write_reg(pll + 5, ((P3&0x000F0000)>>12)|((P2&0x000F0000)>>16));
  si5351_write_reg(pll + 6, (P2 & 0x0000FF00) >> 8);
  si5351_write_reg(pll + 7, (P2 & 0x000000FF));
}
static void setupMultisynth(uint8_t synth, uint32_t divider, uint8_t rDiv)
{
  uint32_t P1;
  uint32_t P2;
  uint32_t P3;
  
  P1 = 128 * divider - 512;
  P2 = 0; // P2 = 0, P3 = 1 forces an integer value for the divider
  P3 = 1;
  
  si5351_write_reg(synth + 0, (P3 & 0x0000FF00) >> 8);
  si5351_write_reg(synth + 1, (P3 & 0x000000FF));
  si5351_write_reg(synth + 2, ((P1 & 0x00030000) >> 16) | rDiv);
  si5351_write_reg(synth + 3, (P1 & 0x0000FF00) >> 8);
  si5351_write_reg(synth + 4, (P1 & 0x000000FF));
  si5351_write_reg(synth + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16));
  si5351_write_reg(synth + 6, (P2 & 0x0000FF00) >> 8);
  si5351_write_reg(synth + 7, (P2 & 0x000000FF));
}

#ifdef FOOP
static uint8_t si5351_write(uint8_t addr, uint8_t data)
{
  uint8_t ob[8];
  ob[0] = addr;
  ob[1] = data;
  myi2cwrite(si5351_dev,ob,2);
  return 0;
}
#endif // FOOP

static bool si5351_init(uint8_t xtal_load_c, uint32_t xo_freq, int32_t corr)
{
  static uint8_t init=1;
  if(init) {
    if(init == 2) return true;
    if(myi2ccheck(SI5351_BUS_BASE_ADDR) == 0) {
      init = 2;
      myDprintf("No si5351 found\n");
      return false;
    }
    init = 0;
    mydelay(20);
    if(myi2cinitdev(SI5351_BUS_BASE_ADDR,0,&si5351_dev)) {
      init = 2;
      myDprintf("failed init\n");
      return false;
    }
    mydelay(20);
  }
  return true;
}
#ifdef FOOP
static void si5351_drive_strength(enum si5351_clock clk, enum si5351_drive drive)
{
  uint8_t reg_val;
  const uint8_t mask = 0x03;
  //reg_val = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk);
  {
    uint8_t buf[2],rbuf[2];
    buf[0] = SI5351_CLK0_CTRL + (uint8_t)clk;
    memset(rbuf,0,sizeof(rbuf));
    if(i2c_master_transmit_receive(si5351_dev,buf,1,rbuf,1,1000)) myDprintf("failed\n");
    reg_val = rbuf[0];
  }  
  reg_val &= ~(mask);
  switch(drive) {
  case SI5351_DRIVE_2MA:
    reg_val |= 0x00;
    break;
  case SI5351_DRIVE_4MA:
   reg_val |= 0x01;
    break;
  case SI5351_DRIVE_6MA:
   reg_val |= 0x02;
    break;
  case SI5351_DRIVE_8MA:
    reg_val |= 0x03;
    break;
  default:
    break;
  }
  si5351_write_reg(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val);
}
#endif // FOOP
static void si5351_output_enable(enum si5351_clock clk, uint8_t enable)
{
  uint8_t reg_val;
  //reg_val = si5351_read(SI5351_OUTPUT_ENABLE_CTRL);
  {
    uint8_t buf[2],rbuf[2];
    buf[0] = SI5351_OUTPUT_ENABLE_CTRL;
    memset(rbuf,0,sizeof(rbuf));
    if(i2c_master_transmit_receive(si5351_dev,buf,1,rbuf,1,1000)) myDprintf("failed\n");
    reg_val = rbuf[0];
  }

  if(enable == 1) {
    reg_val &= ~(1<<(uint8_t)clk);
  } else {
    reg_val |= (1<<(uint8_t)clk);
  }
  si5351_write_reg(SI5351_OUTPUT_ENABLE_CTRL,reg_val);
}

static uint8_t si5351_set_freq(uint64_t freq,enum si5351_clock chn)
{
  uint32_t xtalFreq = XTAL_FREQ;
  double f;
  uint8_t mult;
  uint64_t pllFreq;
  uint32_t l,num,denom,divider;
  static uint8_t lasta_mult=0,lastb_mult=0;
  static uint32_t lasta_num=0,lasta_denom=0,lastb_num=0,lastb_denom=0;
  if(freq == 0) return 0;
  if(freq >= 800000000ULL) divider = (90000000000ULL/freq); // >8mc:900mc max
  if(freq <= 800000000ULL) divider = (40000000000ULL/freq); //<8mc 400mc min  
  myprintf("divider=%d\r\n",divider);
  pllFreq = freq*divider;
  mult = pllFreq/(xtalFreq*100UL);
  //myprintf("PLL freqx100:%lld mult:%d (15-90)\r\n",pllFreq,mult);
  l = pllFreq%(xtalFreq*100UL);
  f = l; // mult is an integer that must be in the range 15..90
  f *= 1048575; // num and denom are the fractional parts
  f /= xtalFreq; // 20 bits (range 0..1048575)
  num = (uint32_t)f; // multiplier = mult + num / denom
  denom = 1048575; // set to maximum
  num = num/100;

  if(chn == 0) {
    if(lastb_mult != mult || lastb_num != num || lastb_denom != denom) {
      myDprintf("Warning: PLL_B getting reprogramed %d/%d/%d->%d/%d/%d\n",
                lastb_mult,lastb_num,lastb_denom,mult,num,denom);
      si5351PLL(SI_SYNTH_PLL_B,mult,num,denom);
      lastb_mult = mult;
      lastb_num = num;
      lastb_denom = denom;
    }
    si5351_write_reg(SI_SPREADSPECTRUM,0x00);
    setupMultisynth(SI_SYNTH_MS_0, divider, SI_R_DIV_1);
    si5351_write_reg(SI_PLL_RESET, 0xA0);
    si5351_write_reg(SI_CLK0_CONTROL, 0x4F | SI_CLK_SRC_PLL_B);    
    myprintf("CLK0 M divider %d\r\n",divider);
  } else if(chn == 1) {

    if(lastb_mult != mult || lastb_num != num || lastb_denom != denom) {
      myDprintf("Warning: PLL_B getting reprogramed %d/%d/%d->%d/%d/%d\n",
                lastb_mult,lastb_num,lastb_denom,mult,num,denom);
      si5351PLL(SI_SYNTH_PLL_B,mult,num,denom);
      lastb_mult = mult;
      lastb_num = num;
      lastb_denom = denom;
    }
    si5351_write_reg(SI_SPREADSPECTRUM,0x00);
    setupMultisynth(SI_SYNTH_MS_1, divider, SI_R_DIV_1);
    si5351_write_reg(SI_PLL_RESET, 0xA0);
    si5351_write_reg(SI_CLK1_CONTROL, 0x4F | SI_CLK_SRC_PLL_B);

    myprintf("CLK1 M divider %d\r\n",divider);
  } else if(chn == 2) {

    if(lasta_mult != mult || lasta_num != num || lasta_denom != denom) {
      myDprintf("Warning: PLL_A getting reprogramed %d/%d/%d->%d/%d/%d\n",
                lasta_mult,lasta_num,lasta_denom,mult,num,denom);
      si5351PLL(SI_SYNTH_PLL_A,mult,num,denom);
      lasta_mult = mult;
      lasta_num = num;
      lasta_denom = denom;
    }
    si5351_write_reg(SI_SPREADSPECTRUM,0x00);
    setupMultisynth(SI_SYNTH_MS_2, divider, SI_R_DIV_1);
    si5351_write_reg(SI_PLL_RESET, 0xA0);
    si5351_write_reg(SI_CLK2_CONTROL, 0x4F | SI_CLK_SRC_PLL_A);

    myprintf("CLK2 M divider %d\r\n",divider);
  }
  return 0;
}
/*end copyright*/
#define CHANNEL_MIXER SI5351_CLK2
#define CHANNEL_ADCCLK SI5351_CLK0
static int dofreq(int argc,char *argv[])
{
  double f,f2;
  uint64_t myfr;
  static uint8_t init=1;

  if(init) {
    init = 0;
    si5351_init(SI5351_CRYSTAL_LOAD_8PF,0,2200);
  }
  f = cur_lo_freq/1000000; // 0.2396; // 4x(60.0-0.1)kc  4x for Tayloe mixer. Low-IF=100Hz
  if(argc > 1) f = atof(argv[1]);
  f2 = cur_fs_freq/1000000; // 0.8192; // I2S MCLK=256*sample_rate.  sample_rate=400*8
  if(argc > 2) f2 = atof(argv[2]);
  
  myfr=(uint64_t)(f*100.0*1000000.0);
  si5351_set_freq(myfr,CHANNEL_MIXER);
  si5351_output_enable(CHANNEL_MIXER,1);
  myprintf("New LO f=%.8f MHz\n",f);
  cur_lo_freq = f*1000000;
  mysleep(1); // seems needed.

  myfr=(uint64_t)(f2*100.0*1000000.0);
  si5351_set_freq(myfr,CHANNEL_ADCCLK);
  si5351_output_enable(CHANNEL_ADCCLK,1);
  myprintf("New ADC MCLK f=%.8f MHz\n",f2);
  cur_fs_freq = f2*1000000;
  mysleep(1);
  
  return 0;
}

/*
 * My WWVB Clock display stuff
 * Copyright notice:
 * SPDX-FileCopyrightText: Copyright (c) 2025-2026 Richard Lamb
 * SPDX-License-Identifier: CC BY-NC-SA-4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
 * Author: RHLamb (W3TOY) wwvb@xtcn.com
 *
 * The above copyright notice shall be included in all copies or substantial portions of this software.
 */
#ifdef CONFIG_IDF_TARGET_ESP32S2
#define DISPLAY_POS_1 320
#endif // CONFIG_IDF_TARGET_ESP32S2
#ifdef CONFIG_IDF_TARGET_ESP32S3
#define DISPLAY_POS_1 640
#endif // CONFIG_IDF_TARGET_ESP32S3
#define EXTCLOCKFACE // pullin an external bitmap clockface from clockface.c

static uint8_t uiinited=1;
#define color_black       0x000000
#define color_white       0xffffff
#define color_grey        0x3f3f3f
#define color_red         0x0000ff
#define color_green       0x00ff00
#define color_darkgreen   0x007f00
#define color_blue        0xff0000
#define color_yellow      0x00f2ff
#define color_cyan        color_green|color_blue
#define color_purple      color_red|color_blue
#define color_pink        0xff69b4 // 0xffc0cb,hot 0xff69b4,deep 0xff1493, shocking 0xfc0fc0
static int lcd_bg_color=color_blue;
//static int lcd_fg_color=color_yellow;

static SemaphoreHandle_t lvgl_mux;

static lv_style_t style_screen;

static lv_obj_t *wwvbbtn,*wwvbled,*gpsbtn,*gpslabel,*wwvblabel,*datelabel;
static lv_obj_t *wwvblabelyr,*wwvblabelsec,*wwvblabeldebug=NULL,*wwvblabelbearing=NULL;
#ifdef EXTCLOCKFACE
#include "clockface.c"
#endif // EXTCLOCKFACE

static lv_point_t chandlp[2],chandmin[2],chandhr[2];
static int cradius;

#define SEGCOR 4
#define SEGLEN 20
#define SEGSPACE 35
typedef struct {
  lv_obj_t *ln[7];
  lv_point_t lp[7][2];
  int init,x,y;
} seg7b;
static void do7seg(int num,seg7b *sb)
{
  static lv_obj_t *scr;
  static lv_style_t style_line;
  static lv_point_t none[2];
  int i;
  int x=sb->x,y=sb->y;
  if(sb->init == 0) {
    sb->init = 1;
    if(scr == NULL) {
      scr = lv_disp_get_scr_act(NULL);
      lv_style_init(&style_line);
      lv_style_set_line_width(&style_line,7);
      lv_style_set_line_color(&style_line,lv_color_hex(color_red));
      lv_style_set_line_rounded(&style_line,true);
    }
    for(i=0;i<7;i++) {
      sb->ln[i] = lv_line_create(scr);
      lv_obj_add_style(sb->ln[i],&style_line,0);
      lv_obj_align(sb->ln[i],LV_ALIGN_TOP_LEFT,0,0);
      switch(i) {
      case 0:
        sb->lp[i][0].x = x; sb->lp[i][0].y = y-SEGCOR;
        sb->lp[i][1].x = x; sb->lp[i][1].y = y-SEGLEN+SEGCOR;
        break;
      case 1:
        sb->lp[i][0].x = x; sb->lp[i][0].y = y-SEGLEN-SEGCOR;
        sb->lp[i][1].x = x; sb->lp[i][1].y = y-(2*SEGLEN)+SEGCOR;
        break;
      case 2:
        sb->lp[i][0].x = x+SEGCOR; sb->lp[i][0].y = y-(2*SEGLEN);
        sb->lp[i][1].x = x+SEGLEN-SEGCOR; sb->lp[i][1].y = y-(2*SEGLEN);
        break;
      case 3:
        sb->lp[i][0].x = x+SEGLEN; sb->lp[i][0].y = y-(2*SEGLEN)+SEGCOR;
        sb->lp[i][1].x = x+SEGLEN; sb->lp[i][1].y = y-SEGLEN-SEGCOR;
        break;
      case 4:
        sb->lp[i][0].x = x+SEGLEN; sb->lp[i][0].y = y-SEGLEN+SEGCOR;
        sb->lp[i][1].x = x+SEGLEN; sb->lp[i][1].y = y-SEGCOR;
        break;
      case 5:
        sb->lp[i][0].x = x+SEGLEN-SEGCOR; sb->lp[i][0].y = y;
        sb->lp[i][1].x = x+SEGCOR; sb->lp[i][1].y = y;
        break;
      case 6:
        sb->lp[i][0].x = x+SEGCOR; sb->lp[i][0].y = y-SEGLEN;
        sb->lp[i][1].x = x+SEGLEN-SEGCOR; sb->lp[i][1].y = y-SEGLEN;
        break;
      }   
    }
  }


  char *segs[]={
    "1111110",
    "0001100",
    "1011011",
    "0011111",
    "0101101",
    "0110111",
    "1110111",
    "0011100",
    "1111111",
    "0111111"
  };
  for(i=0;i<7;i++) {
    char *k=segs[num%10];
    if(k[i] == '1') lv_line_set_points(sb->ln[i],sb->lp[i],2);
    else lv_line_set_points(sb->ln[i],none,2);
  }

}


static void dosiglvl(int lvl)
{
  static lv_obj_t *scr;
  static lv_style_t style_line;
  static lv_obj_t *ln[5];
  static lv_point_t lp[5][2];
  static lv_point_t none[2];
  static uint8_t init=1;
  int i;
  int x=DISPLAY_POS_1+133,y=38; // was 453,38
  if(init) {
    init = 0;
    if(scr == NULL) {
      scr = lv_disp_get_scr_act(NULL);
      lv_style_init(&style_line);
      lv_style_set_line_width(&style_line,3);
      lv_style_set_line_color(&style_line,lv_color_hex(color_grey));
      lv_style_set_line_rounded(&style_line,true);
    }
    for(i=0;i<5;i++) {
      ln[i] = lv_line_create(scr);
      lv_obj_add_style(ln[i],&style_line,0);
      lv_obj_align(ln[i],LV_ALIGN_TOP_LEFT,0,0);      
      lp[i][0].x = x+(i*5); lp[i][0].y = y;
      lp[i][1].x = x+(i*5); lp[i][1].y = y-(5*(i+1));
      lv_line_set_points(ln[i],lp[i],2);
    }
  }
  if(lvl > 0) lv_line_set_points(ln[0],lp[0],2); else lv_line_set_points(ln[0],none,2);
  if(lvl > 1) lv_line_set_points(ln[1],lp[1],2); else lv_line_set_points(ln[1],none,2);
  if(lvl > 2) lv_line_set_points(ln[2],lp[2],2); else lv_line_set_points(ln[2],none,2);
  if(lvl > 3) lv_line_set_points(ln[3],lp[3],2); else lv_line_set_points(ln[3],none,2);
  if(lvl > 4) lv_line_set_points(ln[4],lp[4],2); else lv_line_set_points(ln[4],none,2);
}

static lv_obj_t *line1,*line2,*line3;
static void updateanalog(struct tm *t)
{  
  //struct tm *t=localtime(&curtime);
  static int lastmin=61; // forces 1st display
  
  double d2r=3.1415926536/180.0;
  double phi;
  int i;
  i = (t->tm_sec*6) - 90;
  phi = d2r*i;
  chandlp[1].x = (int)(cradius*cos(phi) + chandlp[0].x);
  chandlp[1].y = (int)(cradius*sin(phi) + chandlp[0].y);
  lv_line_set_points(line1,chandlp,2);
  
  if(lastmin != t->tm_min) {
    i = (t->tm_min*6) - 90;
    phi = d2r*i;
    chandmin[1].x = (int)(cradius*cos(phi) + chandmin[0].x);
    chandmin[1].y = (int)(cradius*sin(phi) + chandmin[0].y);
    lv_line_set_points(line2,chandmin,2);
    lastmin = t->tm_min;

    i = (t->tm_hour*30) + (t->tm_min/2) - 90;
    phi = d2r*i;
    chandhr[1].x = (int)( ((cradius*cos(phi))/2.0) + chandhr[0].x);
    chandhr[1].y = (int)( ((cradius*sin(phi))/2.0) + chandhr[0].y);
    lv_line_set_points(line3,chandhr,2);

  }
}
static void btn_event_cb(lv_event_t * e)
{
  lv_event_code_t code = lv_event_get_code(e);
  lv_obj_t * btn = lv_event_get_target(e);
  if(code == LV_EVENT_CLICKED) {
    static uint8_t cnt = 0;
    cnt++;
    
    /*Get the first child of the button which is the label and change its text*/
    //lv_obj_t * label = lv_obj_get_child(btn, 0);
    //lv_label_set_text_fmt(label, "Button: %d", cnt);
    lv_obj_set_style_bg_color(btn,lv_color_hex(color_green),LV_PART_MAIN);
  }
}

#define DISP_TIMEDATEPOS 35

typedef struct {
  lv_obj_t *ln;
  lv_point_t lp[2];
  int init,x,y;
} mysegdot;

static void drawdot(int x,int y,mysegdot *dt)
{
  static lv_obj_t *scr;
  static lv_style_t style_line;

  if(scr == NULL) {
    scr = lv_disp_get_scr_act(NULL);
    lv_style_init(&style_line);
    lv_style_set_line_width(&style_line,3);
    lv_style_set_line_color(&style_line,lv_color_hex(color_red));
    lv_style_set_line_rounded(&style_line,true);
  }

  dt->ln = lv_line_create(scr);
  lv_obj_add_style(dt->ln,&style_line,0);
  lv_obj_align(dt->ln,LV_ALIGN_TOP_LEFT,0,0);
  dt->lp[0].x = x; dt->lp[0].y = y;
  dt->lp[1].x = x; dt->lp[1].y = y-2;
  lv_line_set_points(dt->ln,dt->lp,2);  
}

static void do7segup(int hr,int min,int sec)
{
  static seg7b sb[6];
  static mysegdot dt[4];
  static int init=1;
  if(init) {
    int x=270; // was 320
    int y=243+DISP_TIMEDATEPOS;
    init = 0;

    sb[0].x = x;
    sb[0].y = y;
    do7seg(0,&sb[0]);
    x += SEGSPACE;
    sb[1].x = x;
    sb[1].y = y;
    do7seg(0,&sb[1]);
    
    x += (SEGSPACE-7);
    drawdot(x,y-10,&dt[0]);
    drawdot(x,y-30,&dt[1]);
    x += 7;
    
    //x += SEGSPACE;
    sb[2].x = x;
    sb[2].y = y;
    do7seg(0,&sb[2]);
    x += SEGSPACE;
    sb[3].x = x;
    sb[3].y = y;
    do7seg(0,&sb[3]);
    x += (SEGSPACE-7);
    drawdot(x,y-10,&dt[2]);
    drawdot(x,y-30,&dt[3]);
    x += 7;
    
    //x += SEGSPACE;
    sb[4].x = x;
    sb[4].y = y;
    do7seg(0,&sb[4]);
    x += SEGSPACE;
    sb[5].x = x;
    sb[5].y = y;
    do7seg(0,&sb[5]);    
  }
 
  static int _hr=24,_min=61,_sec=61;
  if(_hr != hr) {
    _hr = hr;
    do7seg(hr/10,&sb[0]);
    do7seg(hr%10,&sb[1]);
  }
  if(_min != min) {
    _min = min;
    do7seg(min/10,&sb[2]);
    do7seg(min%10,&sb[3]);
  }
  if(_sec != sec) {
    _sec = sec;
    do7seg(sec/10,&sb[4]);
    do7seg(sec%10,&sb[5]);
  }

}

#define MYTERMLEN 900 // keep small: 10 lines x 90 chars/line
static char lcdtext[MYTERMLEN+1];
static void lcdconsole(char *in)
{
  if(uiinited) return; // UI has not been initialized
#ifdef CONFIG_IDF_TARGET_ESP32S2
  return;  // display too small
#endif // CONFIG_IDF_TARGET_ESP32S2
  static uint8_t init=1;
  static lv_obj_t *label;
  static lv_style_t style;
  if(init) {
    init = 0;
    lv_style_init(&style);
    lv_style_set_border_width(&style,2);
    lv_style_set_border_color(&style,lv_color_hex(color_black));
    label = lv_label_create(lv_scr_act());
#ifdef CONFIG_IDF_TARGET_ESP32S2
    lv_obj_set_pos(label,10,320); // off the chart
    lv_obj_set_size(label,700,140);
#endif // CONFIG_IDF_TARGET_ESP32S2
#ifdef CONFIG_IDF_TARGET_ESP32S3    
    lv_obj_set_pos(label,10,320);
    lv_obj_set_size(label,700,140);
#endif // CONFIG_IDF_TARGET_ESP32S3    
    //lv_obj_add_style(label,&style,LV_STATE_DEFAULT);
    lv_obj_set_style_text_font(label,&lv_font_montserrat_14,0);    
    lv_label_set_recolor(label,true);
    lv_label_set_long_mode(label,LV_LABEL_LONG_WRAP);
    lv_label_set_text_static(label,lcdtext);
  }  
  static int olen=0;
  int clen = strlen(in);
  if(clen > MYTERMLEN) {
    in += (clen - MYTERMLEN);
    clen = MYTERMLEN;
    olen = 0;
  } else if((olen+clen) > MYTERMLEN) {
    int i;
    for(i=0;i<olen;i++) {
      if(lcdtext[i] == '\n') {
	if(i >= clen) {
	  while(lcdtext[i] == '\n' || lcdtext[i] == '\r') i++;
	  break;
	}
      }
    }
    if(i == olen) i = olen - (MYTERMLEN - clen);
    for(int j=i;j<olen;j++) lcdtext[j-i] = lcdtext[j];
    olen -= i;
    lcdtext[olen] = '\0';  
  }
  memcpy(&lcdtext[olen],in,clen);
  olen += clen;  
  lcdtext[olen] = '\0';
  lv_label_set_text_static(label,lcdtext);  
}

static int display_inited=0;

static void create_ui(int32_t v)
{
  static lv_obj_t *scr;
  static lv_style_t style_line,style_line2;
  static uint8_t init=1;

  if(init) {
    init = 0;
    lvgl_mux = xSemaphoreCreateMutex();
    
    scr = lv_disp_get_scr_act(NULL);
  
    // Set the background color of the display to black.
    lv_style_init(&style_screen);
    lv_style_set_bg_color(&style_screen,lv_color_hex(lcd_bg_color));
    lv_style_set_text_font(&style_screen,&lv_font_montserrat_28); // bigger default font
    lv_obj_add_style(scr,&style_screen,LV_STATE_DEFAULT);

    chandlp[0].x = 153; // x0
    chandlp[0].y = 148; // y0
    chandmin[0].x = chandlp[0].x;
    chandmin[0].y = chandlp[0].y;
    chandhr[0].x = chandlp[0].x;
    chandhr[0].y = chandlp[0].y;
    cradius = 137;    
#ifdef EXTCLOCKFACE
    uint8_t extclockgot=0;
    lv_obj_t *img=lv_img_create(scr);
    if(fatinit() == 0) {
      FILE *fp;
      char *fname=MYMOUNTPT"/face.bin";
      struct stat st;
      if(stat(fname,&st) == 0) {
	if((fp=fopen(fname,"rb"))) {
	  myDprintf("|%s| %d bytes\n",fname,st.st_size);
	  //LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMG_CLOCKFACE uint8_t *disk_clockface_map=NULL;
	  lv_img_dsc_t *dcf;
	  if((dcf=(lv_img_dsc_t *)mymalloc(sizeof(lv_img_dsc_t)+st.st_size))) {	    
	    memset(dcf,0,sizeof(lv_img_dsc_t));
	    dcf->header.cf = LV_IMG_CF_RGB565A8;
	    dcf->header.w = 308;
	    dcf->header.h = 300;
	    dcf->data = (uint8_t *)(dcf+1);
	    fread((void *)dcf->data,1,4,fp); st.st_size -= 4; // skip first 4 bytes from lvgl bin converter
	    fread((void *)dcf->data,1,st.st_size,fp);
	    dcf->data_size = st.st_size; // 277200
	    lv_img_set_src(img,dcf);
	    extclockgot = 1;
	  }
	  fclose(fp);
	}
      }
    }
    if(extclockgot == 0) {
      LV_IMG_DECLARE(clockface);
      lv_img_set_src(img,&clockface);
    }
#else // EXTCLOCKFACE
    // draw one?
#endif // EXTCLOCKFACE
    
    lv_style_init(&style_line2);
    lv_style_set_line_width(&style_line2,5);
    lv_style_set_line_color(&style_line2,lv_color_hex(color_black));
    lv_style_set_line_rounded(&style_line2,true);

    line2 = lv_line_create(scr);
    lv_obj_add_style(line2,&style_line2,0);
    lv_obj_align(line2,LV_ALIGN_TOP_LEFT,0,0);

    line3 = lv_line_create(scr);
    lv_obj_add_style(line3,&style_line2,0);
    lv_obj_align(line3,LV_ALIGN_TOP_LEFT,0,0);

    lv_style_init(&style_line);
    lv_style_set_line_width(&style_line,5);
    lv_style_set_line_color(&style_line,lv_color_hex(color_red));
    lv_style_set_line_rounded(&style_line,true);

    line1 = lv_line_create(scr);
    lv_obj_add_style(line1,&style_line,0);
    lv_obj_align(line1,LV_ALIGN_TOP_LEFT,0,0);

    char *s;
    lv_obj_t *btn;
    lv_obj_t *label;
    
    s = "#000000 WWVB #";    
    btn = lv_btn_create(scr);  
    lv_obj_set_pos(btn,DISPLAY_POS_1,10); // 320,10 
    lv_obj_set_size(btn,130,30);
    lv_obj_set_style_bg_color(btn,lv_color_hex(color_grey),LV_PART_MAIN);
    lv_obj_add_event_cb(btn,btn_event_cb,LV_EVENT_ALL,NULL);
    label = lv_label_create(btn);
    lv_label_set_recolor(label,true);
    lv_label_set_text(label,s);
    lv_obj_center(label);
    wwvbbtn = btn;

    lv_obj_t *led = lv_led_create(scr);
    lv_obj_set_pos(led,DISPLAY_POS_1-40,13);  // was 280,13 
    lv_led_set_color(led,lv_color_hex(color_red));
    lv_led_off(led);
    wwvbled = led;

    label = lv_label_create(scr);
    lv_obj_set_pos(label,DISPLAY_POS_1,50);
    lv_obj_set_size(label,150,70);
    lv_obj_set_style_text_font(label,&lv_font_montserrat_14,0);
    lv_label_set_recolor(label,true);
    lv_label_set_text(label," ");
    wwvblabel = label;
    
    label = lv_label_create(scr);
    lv_obj_set_pos(label,DISPLAY_POS_1,100);
    lv_obj_set_size(label,150,60);
    lv_obj_set_style_text_color(label,lv_color_hex(color_black),0);
    lv_obj_set_style_text_font(label,&lv_font_montserrat_14,0);
    lv_label_set_text(label," ");
    wwvblabelyr = label;

    label = lv_label_create(scr);
    lv_obj_set_pos(label,DISPLAY_POS_1,115);
    lv_obj_set_size(label,150,60);
    lv_obj_set_style_text_color(label,lv_color_hex(color_red),0);
    lv_obj_set_style_text_font(label,&lv_font_montserrat_18,0);
    lv_label_set_text(label," ");
    wwvblabelsec = label;

#ifdef CONFIG_IDF_TARGET_ESP32S3 // big display
    label = lv_label_create(scr);
    lv_obj_set_pos(label,320,13);
    lv_obj_set_size(label,300,150); // 150,70 
    lv_obj_set_style_text_font(label,&lv_font_montserrat_14,0);
    lv_label_set_recolor(label,true);
    lv_label_set_text(label," ");
    wwvblabelbearing = label;

    label = lv_label_create(scr);
    lv_obj_set_pos(label,320,50);
    lv_obj_set_size(label,300,150); // 150,70
    lv_obj_set_style_text_font(label,&lv_font_montserrat_14,0);
    lv_label_set_recolor(label,true);
    lv_label_set_text(label," ");
    wwvblabeldebug = label;
#endif // CONFIG_IDF_TARGET_ESP32S3

    // one time version display
    label = lv_label_create(scr);
    lv_obj_set_pos(label,10,255+DISP_TIMEDATEPOS);
    lv_obj_set_size(label,100,30);
    lv_obj_set_style_text_font(label,&lv_font_montserrat_28,0);
    lv_label_set_recolor(label,true);
    lv_label_set_text(label,THISVERSION);

    label = lv_label_create(scr);
    lv_obj_set_pos(label,150,255+DISP_TIMEDATEPOS); // 228
    lv_obj_set_size(label,330,30); // 250
    lv_obj_set_style_text_font(label,&lv_font_montserrat_28,0);
    lv_label_set_recolor(label,true);
    lv_label_set_text(label," ");
    datelabel = label;
    
    s = "#000000 GPS #";
    btn = lv_btn_create(scr);
    lv_obj_set_pos(btn,DISPLAY_POS_1,160);
    lv_obj_set_size(btn,130,30);
    lv_obj_set_style_bg_color(btn,lv_color_hex(color_grey),LV_PART_MAIN);
    lv_obj_add_event_cb(btn,btn_event_cb,LV_EVENT_ALL,NULL);
    label = lv_label_create(btn);
    lv_label_set_recolor(label,true);
    lv_label_set_text(label,s);
    lv_obj_center(label);
    gpsbtn = btn;

    label = lv_label_create(scr);
    lv_obj_set_pos(label,DISPLAY_POS_1,190);
    lv_obj_set_size(label,150,70);
    lv_obj_set_style_text_font(label,&lv_font_montserrat_14,0);
    lv_label_set_recolor(label,true);
    lv_label_set_text(label," ");
    gpslabel = label;
    
    uiinited = 0; // let world know UI is ready
    
  }

  
}


/*
 * More wwvb display processing
 *
 * Copyright notice:
 * SPDX-FileCopyrightText: Copyright (c) 2025-2026 Richard Lamb
 * SPDX-License-Identifier: CC BY-NC-SA-4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
 * Author: RHLamb (W3TOY) wwvb@xtcn.com
 *
 * The above copyright notice shall be included in all copies or substantial portions of this software.
 */
static uint8_t timehasbeenset=0;

static char *dststr="";
static char *leapyr="";
static char *leapsec="";
extern char *tzname[2];
typedef struct {
  int goodcnt;
} wwvbstats;
static wwvbstats wwvbstat[24];
static void showwwvb(int testflg,struct tm *tm)
{
  char out[256];
  static int okcnt=0;
  static uint8_t isettime=0;
  
  // if much time has past since last valid wwvb, clear wwvb data and mark yellow
#define WWVB_LONGTIMEOUT
#define WWVB_STALE_SECS 5*3600  // 5 hours?
  static int lastgoodrx=-1; // never seen a good one. leave button grey
  if(lastgoodrx >= 0 && lastgoodrx < WWVB_STALE_SECS) {
    lastgoodrx++;
    if(lastgoodrx >= WWVB_STALE_SECS) { // assume called once per second
      lv_obj_set_style_bg_color(wwvbbtn,lv_color_hex(color_yellow),LV_PART_MAIN);
      lv_label_set_text(wwvblabel,"");
      lv_label_set_text(wwvblabelyr,"");
      lv_label_set_text(wwvblabelsec,"");
    } else if(isettime) {
      lv_obj_set_style_bg_color(wwvbbtn,lv_color_hex(color_green),LV_PART_MAIN);
      isettime = 0;
    }
  }

  int i,j;
  static int last60ok=0;

  if(timehasbeenset) {
    i = (curtime%86400);
    j = i/3600;
    if((i%3600) == 0) wwvbstat[j].goodcnt = 0;
    if((i%60) == 0) {
      if(last60ok) wwvbstat[j].goodcnt++; // max one good per minute
      last60ok = 0;
    }
  }
    
  // from petaluma: utc hrs below/ max = 48
  //     44*11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 34 41 42 46
  //     45 44 45 46 46 42*46 46 43 27 44 44 46 31 43 46 46 44 9  0 15 13 23 41
  // gmt: 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 . 19 20 21 22 23
  // uxk:16 17 18 19 20 21 22 23  0  1  2  3  4  5  6  7  8  9 . 11 .  .  .  .
  out[0] = '\0';
  j = (curtime%86400)/3600;  
  for(i=0;i<24;i++) { // #008000 #cc3366
    if(j == i) sprintf(&out[strlen(out)]," #0000ff %d #",wwvbstat[i].goodcnt);
    else sprintf(&out[strlen(out)]," %d",wwvbstat[i].goodcnt);
  }
  sprintf(&out[strlen(out)],"\n");
  if(wwvblabeldebug) lv_label_set_text(wwvblabeldebug,out);
  int snr=(int)(ext_signal/ext_noise);
  if(snr < 1) j = 0;
  else if(snr < 4) j = 1;
  else if(snr < 7) j = 2;
  else if(snr < 10) j = 3;
  else if(snr < 12) j = 4;
  else j = 5; // best
  dosiglvl(j); // signal meter

  if(wwvbbpskok == 0) return; // no new frame received
  int64_t ptus=wwvbbpskok;
  time_t myt0=wwvbbpsksec;
  wwvbbpskok = 0;

#ifdef FOOP_AMWWVB  
  uint8_t flgs=0,utisgn=0,uticor=0;
  switch((flgs&3)) {
  case 0: dststr = "Standard Time"; break;
  case 1: dststr = "DST ends today"; break;
  case 2: dststr = "DST begins today"; break;
  case 3: dststr = "DST in effect"; break;
  }
  if((flgs&0x8)) leapyr="leap year"; else leapyr="";  
  if((flgs&0x4)) {
    // calculate secs to end of month
    leapsec="leap second at end of month";
  } else leapsec="";
#else // FOOP_AMWWVB
  // https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=914904
  uint8_t nxstdst=0;
  leapsec="no leap seconds";
  switch(wwvbbpskdstls){
  case 8:  // 000x d=00 ls=0-
    dststr = "Standard Time";
    break;
  case 22: // 100x d=10 ls=0-
    dststr = "DST begins today";
    nxstdst = 1;
    break;
  case 3:  // 110x d=11 ls=0-
    dststr = "DST in effect";
    nxstdst = 1;
    break;
  case 21: // 010x d=01 ls=0-
    dststr = "DST ends today";
    break;
  case 4:  // 0010 d=00 ls=10
    dststr = "Standard Time";
    leapsec="-1s @end of month";
    break;
  case 16: // 1010 d=10 ls=10
    dststr = "DST begins today";
    nxstdst = 1;
    leapsec="-1s @end of month";
    break;
  case 13: // 1110 d=11 ls=10
    dststr = "DST in effect";
    nxstdst = 1;
    leapsec="-1s @end of month";
    break;
  case 14: // 0110 d=01 ls=10
    dststr = "DST ends today";
    leapsec="-1s @end of month";
    break;
  case 25: // 0011 d=00 ls=11
    dststr = "Standard Time";
    leapsec="+1s @end of month";
    break;
  case 26: // 1011 d=10 ls=11
    dststr = "DST begins today";
    nxstdst = 1;
    leapsec="+1s @end of month";
    break;
  case 31: // 1111 d=11 ls=11
    dststr = "DST in effect";
    nxstdst = 1;
    leapsec="+1s @end of month";
    break;
  case 28: // 0111 d=01 ls=11
    dststr = "DST ends today";
    leapsec="+1s @end of month";
    break;
  default: // error
    leapsec="Unk";
    dststr = "Unk";
    break;
  }
  
  leapyr="";
  
  // M=first Sunday in March  N=first Sunday in November
#define NXTTAM "first Sunday in March"
#define NXTTAN "first Sunday in November"
  // *=DST transition to occur outside of defined schedules, so no advance notification available.
#define NXTTA1 "after 0:59AM, skip from 1:00AM to 2:00AM"
#define NXTTA2 "after 1:59AM, skip from 2:00AM to 3:00AM"
#define NXTTA3 "after 2:59AM, skip from 3:00AM to 4:00AM"
#define NXTTA4 "after 0:59AM, instead of 1:00AM move back to 0:00AM"
#define NXTTA5 "after 1:59AM, instead of 2:00AM move back to 1:00AM"
#define NXTTA6 "after 2:59AM, instead of 3:00AM move back to 2:00AM"  
  char *nxst="",*nxtta="";
  switch(wwvbbpskndst) {
  case 0x23: nxst="DST transition occurs at different time*"; break;
  case 0x7: nxst="no DST period scheduled this year"; break;
  case 0x2f: nxst="DST in effect for this whole year"; break;
  case 0x30: nxst="reserved 1"; break;
  case 0x24: nxst="reserved 2"; break;
  case 0x14: nxst="reserved 3"; break;
  case 0x36: nxst="reserved 4"; break;
  case 0x35: nxst="reserved 5"; break;
  default: break;
  }
  if(strlen(nxst) > 0) goto nxstproc;
  if(nxstdst == 0) {
    switch(wwvbbpskndst) {
    case 0x31: nxst="1st Sunday of March"; nxtta=NXTTA1; break; // NXTTA1
    case 0x26: nxst="2nd Sunday of March"; nxtta=NXTTA1; break;
    case 0x25: nxst="3rd Sunday of March"; nxtta=NXTTA1; break;
    case 0x15: nxst="4th Sunday of March"; nxtta=NXTTA1; break;
    case 0x3e: nxst="4th Sunday since M"; nxtta=NXTTA1; break;
    case 0x16: nxst="5th Sunday since M"; nxtta=NXTTA1; break;
    case 0x37: nxst="6th Sunday since M"; nxtta=NXTTA1; break;
    case 0x3d: nxst="7th Sunday since M"; nxtta=NXTTA1; break;
      
    case 0x2a: nxst="1st Sunday of March"; nxtta=NXTTA2; break; // NXTTA2
    case 0x1b: nxst="2nd Sunday of March"; nxtta=NXTTA2; break;
    case 0xe: nxst="3rd Sunday of March"; nxtta=NXTTA2; break;
    case 0x1: nxst="4th Sunday of March"; nxtta=NXTTA2; break;
    case 0x2: nxst="4th Sunday since M"; nxtta=NXTTA2; break;
    case 0x8: nxst="5th Sunday since M"; nxtta=NXTTA2; break;
    case 0xd: nxst="6th Sunday since M"; nxtta=NXTTA2; break;
    case 0x29: nxst="7th Sunday since M"; nxtta=NXTTA2; break;
      
    case 0x4: nxst="1st Sunday of March"; nxtta=NXTTA3; break; // NXTTA3
    case 0x20: nxst="2nd Sunday of March"; nxtta=NXTTA3; break;
    case 0x34: nxst="3rd Sunday of March"; nxtta=NXTTA3; break;
    case 0x2c: nxst="4th Sunday of March"; nxtta=NXTTA3; break;
    case 0x38: nxst="4th Sunday since M"; nxtta=NXTTA3; break;
    case 0x10: nxst="5th Sunday since M"; nxtta=NXTTA3; break;
    case 0x32: nxst="6th Sunday since M"; nxtta=NXTTA3; break;
    case 0x1c: nxst="7th Sunday since M"; nxtta=NXTTA3; break;
      
    default: break;
    }
    if(strlen(nxst) == 0) myDprintf("bad dst_next bits A\n");
  } else {
    switch(wwvbbpskndst) {
    case 0x37: nxst="4th Sunday before N"; nxtta=NXTTA4; break; // NXTTA4
    case 0x15: nxst="3rd Sunday before N"; nxtta=NXTTA4; break;
    case 0x31: nxst="2nd Sunday before N"; nxtta=NXTTA4; break;
    case 0x16: nxst="1st Sunday before N"; nxtta=NXTTA4; break;
    case 0x26: nxst="1st Sunday of November"; nxtta=NXTTA4; break;
    case 0x3e: nxst="2nd Sunday of November"; nxtta=NXTTA4; break;
    case 0x25: nxst="3rd Sunday of November"; nxtta=NXTTA4; break;
    case 0x3d: nxst="4th Sunday of November"; nxtta=NXTTA4; break;
      
    case 0xd: nxst="4th Sunday before N"; nxtta=NXTTA5; break; // NXTTA5
    case 0x1: nxst="3rd Sunday before N"; nxtta=NXTTA5; break;
    case 0x2a: nxst="2nd Sunday before N"; nxtta=NXTTA5; break;
    case 0x8: nxst="1st Sunday before N"; nxtta=NXTTA5; break;
    case 0x1b: nxst="1st Sunday of November"; nxtta=NXTTA5; break;
    case 0x2: nxst="2nd Sunday of November"; nxtta=NXTTA5; break;
    case 0xe: nxst="3rd Sunday of November"; nxtta=NXTTA5; break;
    case 0x29: nxst="4th Sunday of November"; nxtta=NXTTA5; break;
      
    case 0x32: nxst="4th Sunday before N"; nxtta=NXTTA6; break; // NXTTA6
    case 0x2c: nxst="3rd Sunday before N"; nxtta=NXTTA6; break;
    case 0x4: nxst="2nd Sunday before N"; nxtta=NXTTA6; break;
    case 0x10: nxst="1st Sunday before N"; nxtta=NXTTA6; break;
    case 0x20: nxst="1st Sunday of November"; nxtta=NXTTA6; break;
    case 0x38: nxst="2nd Sunday of November"; nxtta=NXTTA6; break;
    case 0x34: nxst="3rd Sunday of November"; nxtta=NXTTA6; break;
    case 0x1c: nxst="4th Sunday of November"; nxtta=NXTTA6; break;
      
    default: break;
    }
    if(strlen(nxst) == 0) myDprintf("bad dst_next bits B\n");
  }
 nxstproc:

  if(wwvbbpskdstls0 != wwvbbpskdstls) {
    myDprintf("Updated DST/leap info:%s/%s\n",dststr,leapsec);
    wwvbbpskdstls0 = wwvbbpskdstls;
    myDprintf("Updated DST detail info:%s/%s\n",nxst,nxtta);
    wwvbbpskndst0 = wwvbbpskndst;
  }
  if(wwvbbpskndst0 != wwvbbpskndst) { // possible?
    myDprintf("Updated DST detail info only:%s/%s\n",nxst,nxtta);
    wwvbbpskndst0 = wwvbbpskndst;
  }
#endif // FOOP_AMWWVB

#ifdef FOOP_AMWWVB
  // input from AM WWVB signal: yr,days,hr,min
  int mon=0,day=0;
  yr += 2000;
  cvt_yd_to_ymd(yr,days,&mon,&day);
  time_t myt0 = (time_t)date2secs(yr,mon-1,day-1,hr,min,0); // cannot use mktime()=localtime
#endif // FOOP_AMWWVB
  
  time_t myt=myt0;
  
  // initial settime hueristic: 3 readings in a row
  static time_t lastmyt=0;
#ifdef FOOP_AMWWVB
  // readings ~60s apart means good okcnt++
  if(myt < (lastmyt+58) || (int)(myt - lastmyt) > 61) okcnt = 0; else okcnt++;
#else // FOOP_AMWWVB
  // https://en.wikipedia.org/wiki/WWVB
  // BPSK only does 48/hr due to special at 10-16/40-46min 360bit=|pn(127)|sync(106)|rev-pr(127)|
  // and 10% msg frames with sync=0 1101000111010 (vs regular 0 0011101101000)
  // So need to change below.
  if(myt < (lastmyt+58) || (int)(myt - lastmyt) > 61) okcnt = 0; else okcnt++;
#endif // FOOP_AMWWVB
  if(testflg == 3) okcnt = 3; // force if test
  lastmyt = myt;
  
  // get cpu time
  time_t ltmp;
  struct timeval ltv;
  gettimeofday(&ltv,NULL);
  ltmp = ltv.tv_sec;
  int64_t tdifus = (ltv.tv_sec*1000000) + ltv.tv_usec;
  // correct for procesing delay
  int64_t mytinus = 1000000*myt;
  int64_t tus=esp_timer_get_time();
  tus -= ptus; // time to now from received frame timestamp  
  mytinus += tus;  
  myt = mytinus/1000000;
  suseconds_t mytus = mytinus%1000000;
  // compute sec and usec diffs
  int tdif=(int)(ltmp - myt);
  tdifus -= mytinus;

#ifdef FOOP_AMWWVB
  if(okcnt > 2) {
    lastgoodrx = 0;
    if(abs(tdif) > 1) { // was >=10 correct chip time if its far off and 3 seq wwvb decodes 
      struct timeval tv;
      tv.tv_sec = myt;
      tv.tv_usec = mytus; // -1 to 1,000,000
      settimeofday(&tv,NULL);
      isettime = 1;
      myprintf("wwvb: 3X set time (dif=%d)\n",tdif);
      lv_obj_set_style_bg_color(wwvbbtn,lv_color_hex(color_pink),LV_PART_MAIN);
    } else {
      myprintf("wwvb: 3X time good\n");
      lv_obj_set_style_bg_color(wwvbbtn,lv_color_hex(color_green),LV_PART_MAIN);
    }
    if(timehasbeenset == 0) {
      timehasbeenset = 1;
      lv_style_set_bg_color(&style_screen,lv_color_hex(color_white));
      lv_obj_add_style(lv_disp_get_scr_act(NULL),&style_screen,LV_STATE_DEFAULT);
    }
    //lv_obj_set_style_bg_color(wwvbbtn,lv_color_hex(color_green),LV_PART_MAIN);
  } else {
    if(abs(tdif) < 3) {
      lastgoodrx = 0;
      struct timeval tv;
      tv.tv_sec = myt;
      tv.tv_usec = mytus;
      settimeofday(&tv,NULL);
      isettime = 1;
      myprintf("wwvb: adjust time (dif=%d)\n",tdif);
      lv_obj_set_style_bg_color(wwvbbtn,lv_color_hex(color_pink),LV_PART_MAIN);
    } else {
#ifndef WWVB_LONGTIMEOUT      
      lv_obj_set_style_bg_color(wwvbbtn,lv_color_hex(color_cyan),LV_PART_MAIN);
#endif // WWVB_LONGTIMEOUT
      myprintf("wwvb: too far off (dif=%d)\n",tdif);      
    }
  }

#else // FOOP_AMWWVB
  
  // for BPSK I consider "good" if ecc checks and
  // if timehasbeenset
  //    if within 2 sec of RTC=>settime
  // else
  //    if 3 consecutive matches=>settime and set timehasbeenset
  // I have found that 3 consecutive wrong but matching times can happen
  // ..so must accept set time even after timehasbeenset.
  if(timehasbeenset) {
    if(abs(tdif) < 3 || okcnt > 2) {
      lastgoodrx = 0;
      struct timeval tv;
      tv.tv_sec = myt;
      tv.tv_usec = mytus;
      settimeofday(&tv,NULL);
      isettime = 1;
      if(abs(tdif) >= 3) myprintf("wwvb: 3X set time B (dif=%d)\n",tdif);
      else myprintf("wwvb: adjust time (dif=%d)\n",tdif);
      lv_obj_set_style_bg_color(wwvbbtn,lv_color_hex(color_pink),LV_PART_MAIN);
    } else {
      myprintf("wwvb: too far off (dif=%d)\n",tdif);
    }
  } else if(okcnt > 2) {
    lastgoodrx = 0;
    if(abs(tdif) > 1) { // was >=10 correct chip time if its far off and 3 seq wwvb decodes
      struct timeval tv;
      tv.tv_sec = myt;
      tv.tv_usec = mytus; // -1 to 1,000,000
      settimeofday(&tv,NULL);
      isettime = 1;
      myprintf("wwvb: 3X set time A (dif=%d)\n",tdif);
      lv_obj_set_style_bg_color(wwvbbtn,lv_color_hex(color_pink),LV_PART_MAIN);
    } else {
      myprintf("wwvb: 3X time good - no set\n");
      lv_obj_set_style_bg_color(wwvbbtn,lv_color_hex(color_green),LV_PART_MAIN);
    }
    timehasbeenset = 1;
    curtime = time(NULL);
    i = (curtime%86400);
    wwvbstat[i/3600].goodcnt = 0; // zero goodcnt for clean start    
    lv_style_set_bg_color(&style_screen,lv_color_hex(color_white));
    lv_obj_add_style(lv_disp_get_scr_act(NULL),&style_screen,LV_STATE_DEFAULT);
  } else {
    // do nothing
  }
#endif // FOOP_AMWWVB


#ifdef FOOP  
  myprintf("wwvb:"); for(i=0;i<60;i++) myprintf("%c",p[i]); myprintf("\n");
  myprintf("wwvb:%04u %02u %02u %02u:%02u:00 cor=%.2fs dif=%.2fs corrected=%s",
           yr,mon,day,hr,min,
           ((float)(tus/10000))/100.0,
           (float)tdifus/1000000.0,ctime(&myt));
  myprintf("wwvb: dut1:%s0.%ds %s %s %s err=%d %s",(utisgn&0x2)?"-":"+",uticor,
           leapyr,leapsec,dststr,wwvbferr,ctime(&ltmp));
#endif // FOOP

  // display misc.
  if(lastgoodrx == 0) { // only if good
    struct tm *tm=gmtime(&myt0);
    strftime(out,sizeof(out),"UTC:%a %b %e, %Y %H:%M",tm);
#ifdef FOOP    
    sprintf(&out[strlen(out)]," dif=%.2fs UT1:%s0.%ds\n%s",
	    (float)tdifus/1000000.0,(utisgn&0x2)?"-":"+",uticor,dststr);
#endif // FOOP
    sprintf(&out[strlen(out)]," dif=%.2fs %s %s\n",
	    (float)tdifus/1000000.0,dststr,
	    wwvbbpsknotice?"N":"-");
    lv_label_set_text(wwvblabel,out);
    lv_label_set_text(wwvblabelyr,leapyr);
    lv_label_set_text(wwvblabelsec,leapsec);
  }

  myDprintf("me-wwvb=%ldus\n",(int32_t)tdifus);
  
  if(abs(tdif) < 10) { // count as good rx if decode is close to chip time
    last60ok++;
  }
}

static void wwvbhourly(char *out)
{
  out[0] = '\0';
  int j=(curtime%86400)/3600;
  for(int i=0;i<24;i++) sprintf(&out[strlen(out)],"%s%d",j==i?"*":" ",wwvbstat[i].goodcnt);
}

/*
 * Persistent storage
 * caution: no task switching while this goes on. else corrupts fatfs
 */
static int configupdate(void)
{
  if(fatinit() || disable_spi_ops) return -1;
  FILE *fp;
  char *fname=MYMOUNTPT"/config.tmp"; // was ".tmp"  
  if((fp=fopen(fname,"w")) == NULL) {
    myDprintf("Cannot open or create |%s|\n",fname);
    return -1;
  }
  time_t _tt=curtime;
  fprintf(fp,"curtime=%lu\n",timehasbeenset?(uint32_t)_tt:(uint32_t)curtime0);
  fprintf(fp,"tz=%s\n",config_tz); // at change
  fprintf(fp,"mode=%d\n",config_mode); // at change
  fprintf(fp,"lofreq=%.2f\n",cur_lo_freq);
  fprintf(fp,"sampfreq=%.2f\n",cur_fs_freq);
  fprintf(fp,"vcxovolt=%.3f\n",vcxovolt);  
  fprintf(fp,"wwvbstats="); // once an hour
  for(int i=0;i<24;i++) fprintf(fp,"%d ",wwvbstat[i].goodcnt);
  //for(int i=0;i<24;i++) fprintf(fp,"%.0f/%.2f ",wwvbstat[i].good,wwvbstat[i].snr);
  fprintf(fp,"\n");
#ifdef DO_WIFI
  if(ssid) fprintf(fp,"ssid=%s\n",ssid);
  if(wifipass) fprintf(fp,"wifipass=%s\n",wifipass);
  if(ntpserver) fprintf(fp,"ntpserver=%s\n",ntpserver);
#endif // DO_WIFI
  fprintf(fp,"rotate=%d\n",rotatedisp);
  fclose(fp);
  
  // esp32 rename() does not replace the dest file.
  if(unlink(MYMOUNTPT"/config.txt")) myDprintf("unlink failed\n");
  if(rename(fname,MYMOUNTPT"/config.txt")) myDprintf("rename failed\n");
  
  return 0;
}
static int configload(void)
{
  if(fatinit() || disable_spi_ops) return -1;
  FILE *fp;
  char *fname=MYMOUNTPT"/config.txt";
  if((fp=fopen(fname,"r")) == NULL) {
    if(configupdate()) return -1;
    if((fp=fopen(fname,"r")) == NULL) {
      myDprintf("Cannot open or create |%s|\n",fname);
      return -1;
    }
  }
  char buf[1024];
  while(fgets(buf,sizeof(buf),fp)) {
    if(buf[0] == '#') continue;
    char *args[4];
    if(lparse(buf,args,4,'=') < 2) continue;
    if(strcmp(args[0],"tz") == 0) {
      strcpy(config_tz,args[1]);
      setenv("TZ",config_tz,1);
      tzset();
    } else if(strcmp(args[0],"mode") == 0) {
      config_mode = atoi(args[1]);
    } else if(strcmp(args[0],"lofreq") == 0) {
      cur_lo_freq = atof(args[1]);
    } else if(strcmp(args[0],"sampfreq") == 0) {
      cur_fs_freq = atof(args[1]);
    } else if(strcmp(args[0],"vcxovolt") == 0) {
      vcxovolt = atof(args[1]);
    } else if(strcmp(args[0],"curtime") == 0) {
      curtime0 = (time_t)atol(args[1]);
    } else if(strcmp(args[0],"wwvbstats") == 0) {
      char *ws[50];
      int n=lparse(args[1],ws,50,' ');
      for(int i=0;i<n;i++) wwvbstat[i].goodcnt = atoi(ws[i]);
#ifdef DO_WIFI
    } else if(strcmp(args[0],"ssid") == 0) {
      if(ssid) myfree(ssid);
      ssid = mystrdup(args[1]);
    } else if(strcmp(args[0],"wifipass") == 0) {
      if(wifipass) myfree(wifipass);
      wifipass = mystrdup(args[1]);
    } else if(strcmp(args[0],"ntpserver") == 0) {
      if(ntpserver) myfree(ntpserver);
      ntpserver = mystrdup(args[1]);      
#endif // DO_WIFI
    } else if(strcmp(args[0],"rotate") == 0) {
      rotatedisp = atoi(args[1]);
    } else {
      myDprintf("unknown field |%s|\n",args[0]);
    }
  }
  fclose(fp);
  return 0;
}
static int log2disk(void)
{
  if(fatinit() || disable_spi_ops) return -1;
  FILE *fp;
  char *fname=MYMOUNTPT"/log.txt";
  if((fp=fopen(fname,"a")) == NULL) {
    myDprintf("Cannot open or create |%s|\n",fname);
    return -1;
  }
  char out[132];
  wwvbhourly(out);
  if(timehasbeenset) {
    time_t _tt=curtime;
    fprintf(fp,"%08lx %s\n",(uint32_t)_tt,out);
  }
  fclose(fp);
  return 0;
} 


#ifdef DO_GPS

#define PIRAD (3.14159/180)
static double berdist(char *out,double lat1,double lon1,double lat2,double lon2)
{
  double t=(lon1-lon2)*PIRAD;
  lat1 *= PIRAD;
  lat2 *= PIRAD;
  double d = sin(lat1)*sin(lat2) + cos(lat1)*cos(lat2)*cos(t);
  d = acos(d);
  d /= PIRAD;
  d *= 60*1.1515;
#ifdef FOOP
  switch(u) {
  case 'M': break;
  case 'K': d *= 1.609344; break;
  case 'N': d *= 0.8684; break;
  }
#endif // FOOP
  
  double x = cos(lat2)*sin(t);
  double y = cos(lat1)*sin(lat2) - sin(lat1)*cos(lat2)*cos(t);
  double b = -atan2(x,y)/PIRAD;

  double delay = 1000*(d*1.609344)/299250;
  sprintf(out,"dist:%.2fmi bearing:%.2fdeg delay:%.2fms\n",d,b,delay);
  return 0;
}


/*
                 X
$GPRMC,201207.00,A,3813.73929,N,12239.23608,W,0.875,,200226,,,A*61
$GPVTG,,T,,M,0.875,N,1.621,K,A*2D
$GPGGA,201207.00,3813.73929,N,12239.23608,W,1,07,1.17,42.8,M,-29.3,M,,*58
$GPGSA,A,3,22,06,11,24,17,19,12,,,,,,2.92,1.17,2.67*02
$GPGSV,3,1,12,03,02,042,,06,59,105,33,11,39,172,31,12,39,305,28*78
$GPGSV,3,2,12,15,00,223,,17,27,052,15,19,54,042,29,22,28,095,30*7D
$GPGSV,3,3,12,24,58,259,26,25,02,296,,32,00,328,,48,44,196,30*7A
$GPGLL,3813.73929,N,12239.23608,W,201207.00,A,A*76

$GPRMC,052650.00,V,,,,,,,030724,,,N*7B
$GPVTG,,,,,,,,,N*30
$GPGGA,052650.00,,,,,0,00,99.99,,,,,,*62
$GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30
$GPGSV,4,1,16,01,,,27,03,30,173,,04,64,079,,06,10,266,28*4D
$GPGSV,4,2,16,07,46,265,25,08,08,134,,09,64,328,27,11,07,304,30*79
$GPGSV,4,3,16,16,44,062,29,20,02,327,30,26,12,042,29,27,12,103,*7A
$GPGSV,4,4,16,30,15,253,,46,45,190,29,47,15,249,28,51,43,156,28*74
$GPGLL,,,,,052650.00,V,N*4E

$GPRMC,052651.00,V,,,,,,,030724,,,N*7A
$GPVTG,,,,,,,,,N*30
$GPGGA,052651.00,,,,,0,00,99.99,,,,,,*63
$GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30
$GPGSV,4,1,16,01,,,26,03,30,173,,04,64,079,,06,10,266,27*43
$GPGSV,4,2,16,07,46,265,,08,08,134,,09,64,328,26,11,07,304,27*79
$GPGSV,4,3,16,16,44,062,25,20,02,327,,26,12,042,26,27,12,103,25*7D
$GPGSV,4,4,16,30,15,253,,46,45,190,29,47,15,249,29,51,43,156,28*75
$GPGLL,,,,,052651.00,V,N*4F

$GPRMC,052652.00,V,,,,,,,030724,,,N*79
$GPVTG,,,,,,,,,N*30
$GPGGA,052652.00,,,,,0,00,99.99,,,,,,*60
*/

static int32_t parse_deg(char *str)
{
  char *p;
  uint32_t lod=atol(str);
  uint32_t h1000thsomin=(lod%100UL)*100000UL;
  if((p=strchr(str,'.'))) {
    unsigned long mult=10000;
    while(*++p) {
      h1000thsomin += mult*(*p - '0');
      mult /= 10;
    }
  }
  return (lod/100)*1000000 + (h1000thsomin+3)/6;
}

static int latlon2tz(char *out,float lat,float lon);
static int tztestdbg=0;
#ifdef FOOP
typedef struct {
  char *name;
  float lat;
  float lon;
} tztestplaces;
static tztestplaces tzplaces[]={
  "indiana",39.625,-86.329,
  "saipan",15.149287502888052, 145.78922506218228,
  "diego garcia",-7.312920067489727, 72.41673198073246,
  "LYR",78.18932594810936, 15.737775737426379,
  "DTH",36.33804206034484, -116.57301843352872,
  "MIT",42.36083618016797, -71.09235219251453,
  "UXK",38.258481637656416, -122.60930914562935,
  "BDA",32.32855574090017, -64.83540938784256,
  NULL,0,0
};
#endif // FOOP

/*
 * Copyright notice:
 * SPDX-FileCopyrightText: Copyright (c) 2025-2026 Richard Lamb
 * SPDX-License-Identifier: CC BY-NC-SA-4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
 * Author: RHLamb (W3TOY) wwvb@xtcn.com
 *
 * The above copyright notice shall be included in all copies or substantial portions of this software.
 */
static uint8_t gpsdebug=0;
static int gpsinactive=0;
static uint8_t gpsok=0;
static uint8_t gpsdouptime=0; // force settime
static int gpsdiff=0; // 2; // gps/utc leapsec differences
static int gpssatcnt=0; // satcnt
static int gpsrx=0;
static double gpslat,gpslon;
static uint8_t gpspos=0;
static int64_t gpsdiffme=0;
static int gpschecksum(char *l)
{
  char *p=strrchr(l,'*');
  if(p == NULL) return -1;
  uint8_t ck0=(uint8_t)(strtoul(p+1,NULL,16)&0xFF);
  uint8_t ck=0;
  for(char *q=(l+1);q != p;q++) ck ^= (uint8_t)*q;
  if(ck != ck0) {
    myDprintf("Bad checksum %02x!= %02x\n",ck,ck0);
    return -1;
  }
  return 0;
}
static int gpsbufproc(char *lbuf)
{
  // |$GPRMC,204045.00,A,3813.74117,N,12239.23512,W,0.146,,110524,,,A*64| 
  // |$GPGGA,204045.00,3813.74117,N,12239.23512,W,1,07,1.14,27.9,M,-29.3,M,,*52|
  // $GPUTC ?  $PTNL,EVT,hhmmss.ssssss,E,NNNNNN,WWWW,D,OO,*  $PTNL,EVT,221213.000008,1,5027,1893,1,17*4F
  // $PGRMF
  /*
NEO-6M module(rom 7.03) gpgga time data is always faster 2 seconds, why?

Did you wait up to 13 minutes for the leap-second values to be updated? The stored time difference between UTC and GPS for u-blox 6-based receivers is smaller than the current time difference between UTC and GPS because of leap-second events that occured after the u-blox 6 was built. The difference between UTC and GPS time is updated at some point within the 12.5 minute (under good signal conditions) GPS data download cycle. When the UTC-GPS time difference update occurs, you should see a jump in UTC time in the NMEA messages.

char ubx_ck_a, ubx_ck_b;
void ubxWrite(char c) {  /// Send byte and calculate checksum.
  gps_ss.write(c);
  ubx_ck_a += c;
  ubx_ck_b += ubx_ck_a;
}
void ubxZeros(byte n) {  /// Send n zeros.
  while (n--) ubxWrite(0x00);
}
void ubxSetLeap() {
  gps_ss.write(0xB5);  /// Sync char 1, micro
  gps_ss.write(0x62);  /// Sync char 2, B
  char ubx_header[] = "\x0B\x02\x48\x00";  /// Class (AID), id (HUI), length (72, little endian)
  ubx_ck_a = ubx_ck_b = 0;  /// Checksum.
  for (byte i=0; i<4; i++) ubxWrite(ubx_header[i]);
  ubxZeros(32);
  ubxWrite(gps_utc_leap_seconds);  /// utcLSF Lo
  ubxWrite(0x00);                  /// utcLSF Hi
  ubxZeros(34);
  ubxWrite(0x02);  /// Flags Lo, 32 bits, 0x02 = utc parameters in message are valid.
  ubxZeros(3);     /// Flags Hi
  gps_ss.write(ubx_ck_a);
  gps_ss.write(ubx_ck_b);
}
UBX-NAV-TIMEUTC UBX-TIMELS  6 January 1980, 
   */

  int ret=0;
    
  if(gpsdebug == 1) myDprintf("%s\n",lbuf);
  
  if(gpschecksum(lbuf)) {
    myDprintf("bad checksum\n");
    return ret;
  }
  gpsinactive = 0; // a gps watchdog

#define LEAPSECCORPERIOD 760 // 12.5 min for GPS UTC leap second correction nav msg
  char *args[20];
  int k = lparse(lbuf,args,20,',');
  if(strcmp(args[0],"$GPGSV") == 0) {
    if(args[3]) gpssatcnt = atoi(args[3]);
    return ret;
  }
  
  if(strncmp(lbuf,"$GPRMC",6)) return ret;

  static int gpsoffset=0; // not used.  will wait for gps to tell me
  static uint32_t lastxx=0;
  
  // $GPRMC,050822.00,A,3813.74130,N,12239.23720,W,0.075,,120925,,,A*67
  if(k > 8 && strcmp(args[2],"A") == 0) {

    gpsrx = 1;
    
    uint32_t _date=atol(args[9]); // 110524
    uint32_t _time=100*atol(args[1]); // 204045.00          
    int yr = _date%100;
    yr += yr>80?1900:2000;    
    uint32_t xx = date2secs(yr,((_date/100)%100) - 1,(_date/10000) - 1,
                            _time/1000000,(_time/10000)%100,
                            (_time/100)%100); // cannot use mktime=locatlime

    // need to compensate here for delay due to 9600baud reception of RMC message
    // 68chars ~= 680 bits(stop..etc) = 0.07s.  so look for 100ms delay or maybe 350ms for 1Hz rate

    time_t ltmp=time(NULL);
    gpsdiff =(int)((xx+gpsoffset) - (uint32_t)ltmp);

    
    // try to indicate when gps leap sec nav messgage correction received ~-3sec
    lastxx += 1; // assume sequential secs
    int _df=(int)(xx-lastxx);
    int _adf=abs(_df);
    if(_adf > 1 && _adf < 60) myDprintf("Maybe leap sec correction %ds received\n",_df);
    lastxx = xx;

#define GPS_PROC_DELAY_MS 200    
    {
      struct timeval tv2;
      gettimeofday(&tv2,NULL);
      int64_t tt1=((xx+gpsoffset)*1000000) + GPS_PROC_DELAY_MS*1000;
      int64_t tt2=(tv2.tv_sec*1000000) + tv2.tv_usec;
      gpsdiffme = tt2 - tt1;
    }
    
    if((abs(gpsdiff) > 1 && gpsok) || // internal clock is off
       (gpsok && timehasbeenset == 0) ||  // time has not been set
       gpsdouptime) {
      gpsdouptime = 0;
      
      struct timeval tv;      
      tv.tv_sec = (time_t)xx;
      tv.tv_sec += gpsoffset; // gps leap seconds
      
      //tv.tv_usec = (_time%100)*10000;  // 100th of sec always 0 for ublox neo-6M
      tv.tv_usec = GPS_PROC_DELAY_MS*1000; // got 125ms from https://hackaday.io/project/170633-vqb-71-gps-clock/log/196290-ticking-accurately-with-the-neo-6m
      
      if(settimeofday(&tv,NULL)) {
        myDprintf("Could not settime\n");
      }
      
      myDprintf("Fix time dif=%ds =(gps+off)-time() gps=%u\r\n",gpsdiff,xx); // 20240511213343.00
      myDprintf("%04u%02u%02u%02u%02u%02u.%02u\r\n",
               yr,(_date/100)%100,_date/10000,
               _time/1000000,(_time/10000)%100,
               (_time/100)%100,
               _time%100);

      if(timehasbeenset == 0) {
        timehasbeenset++;
        lv_style_set_bg_color(&style_screen,lv_color_hex(color_white));
        lv_obj_add_style(lv_disp_get_scr_act(NULL),&style_screen,LV_STATE_DEFAULT);
      }      
    }
    
    int32_t xlat,xlon;
    xlat = parse_deg(args[3]);
    if(strcmp(args[4],"S") == 0) xlat = -xlat;
    xlon = parse_deg(args[5]);
    if(strcmp(args[6],"W") == 0) xlon = -xlon;
    //updated location CM88 38.2290,-122.6539    CM88 38.2290,-122.6540
    double _flat = (double)xlat/1000000.0;
    double _flon = (double)xlon/1000000.0;          
    if(fabs(_flat-gpslat) > 0.0003 || fabs(_flon-gpslon) > 0.0003) {
      gpslat = _flat;
      gpslon = _flon;
      gpspos = 1;
      //myDprintf("%f %f\r\n",_flat,_flon);
    }
  } else {
    gpsrx = -1;
    lastxx = 0;
  }
  return ret;
}
static uint8_t gpswait4leap=1;
static void gpsdisp(void)
{
  char out[80];
  static int gpsgood=0;

  gpsok = 0;
  sprintf(out," ");
  if(gpsinactive++ > 10) {
    lv_obj_set_style_bg_color(gpsbtn,lv_color_hex(color_grey),LV_PART_MAIN);
    if(gpsrx) myDprintf("GPS Disconnected\n");
    gpsrx = 0;
    gpsgood = 0;
    gpswait4leap = 1;
  } else if(gpsrx < 0) {
    lv_obj_set_style_bg_color(gpsbtn,lv_color_hex(color_red),LV_PART_MAIN);
    sprintf(out,"sats:%d",gpssatcnt);
    if(gpsgood) {
      myDprintf("GPS Lost\n");
      gpswait4leap = 1; // fix:check to see if gps time changed much from last good before marking bad 
    }
    gpsgood = 0;
  } else if(gpsrx == 0) {
    sprintf(out,"sats:%d",gpssatcnt);
  } else if(gpsinactive > 1) {
    // do nothing - pause
  } else if(gpsgood++ < LEAPSECCORPERIOD) {
    lv_obj_set_style_bg_color(gpsbtn,lv_color_hex(color_yellow),LV_PART_MAIN);
    sprintf(out,"sats:%d Waiting for leap sec fix %d",gpssatcnt,(LEAPSECCORPERIOD - gpsgood));
    gpswait4leap = 1;
    if(gpsgood == 2) myDprintf("GPS Locked\n");    
  } else {    
    gpsok = 1;
    lv_obj_set_style_bg_color(gpsbtn,lv_color_hex(color_green),LV_PART_MAIN);
    sprintf(out,"sats:%d",gpssatcnt);
    if(gpsgood == (LEAPSECCORPERIOD+1)) {
      gpswait4leap = 0;
      myDprintf("GPS UTC Corrected\n");
    }
  }
  lv_label_set_text(gpslabel,out);

  if(gpspos && tztestdbg == 0) {
    gpspos = 0;
    myDprintf("%.3f %.3f\n",gpslat,gpslon);
    
    if(latlon2tz(out,gpslat,gpslon) == 0) { // slow! will lose data
      myprintf("%s:%s\n","current",out);
      setenv("TZ",out,1);
      tzset();
      strcpy(config_tz,out);
      if(dodiskstuff == NULL) {
	sprintf(out,"update");
	dodiskstuff = mystrdup(out);
      }
    }

    sprintf(out,"wwvb xmtr:");
    berdist(&out[strlen(out)],gpslat,gpslon,40.680722,-105.050472);
    if(wwvblabelbearing) lv_label_set_text(wwvblabelbearing,out);
  }

  static int nsecs=0;
  if(gpsdiffme && nsecs++ > 10) {
    myDprintf("me-gps=%ldus\n",(int32_t)gpsdiffme);
    gpsdiffme = 0;
    nsecs = 0;
  }
  
}
static void gpsstats(void)
{
  char out[80];
  myprintf("satellites:%d %s lat=%.3f lon=%.3f me-gps:%ldus\n",
	   gpssatcnt,
	   gpswait4leap?"waiting for leap sec correction":"",
	   gpslat,gpslon,
	   (int32_t)gpsdiffme);
  berdist(out,gpslat,gpslon,40.680722,-105.050472);
  // WWVB antennas = 40.680722, -105.050472 40.674694, -105.045083
  myprintf("wwvb transmitter:%s\n",out);
}

#ifdef CONFIG_IDF_TARGET_ESP32S3
#define GPSUARTNUM UART_NUM_1
#define GPS_RX_PIN 38 // if not used by vcxo
#define GPS_TX_PIN -1
#endif // CONFIG_IDF_TARGET_ESP32S3

#ifdef CONFIG_IDF_TARGET_ESP32S2
#define GPSUARTNUM UART_NUM_1
#define GPS_RX_PIN 10
#define GPS_TX_PIN -1
#endif // CONFIG_IDF_TARGET_ESP32S2

static int gpsproc(void)
{
  static uint8_t init=1;
  if(init) {
    uart_config_t uart_config = {
      .baud_rate = 9600,
      .data_bits = UART_DATA_8_BITS,
      .parity    = UART_PARITY_DISABLE,
      .stop_bits = UART_STOP_BITS_1,
      .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
    };
    ESP_ERROR_CHECK(uart_param_config(GPSUARTNUM,&uart_config));
    //uart_driver_install(GPSUARTNUM,UART_HW_FIFO_LEN(GPSUARTNUM)+8,0,0,NULL,0);
    // one gps nmea block = ~700bytes
    ESP_ERROR_CHECK(uart_driver_install(GPSUARTNUM,1024,0,0,NULL,0)); // (uart,rxbuflen,txbuflen,qsize,queue,0);
    
    // uart_set_pin(uart_port_t uart_num, int tx_io_num, int rx_io_num, int rts_io_num, int cts_io_num)
    if(uart_set_pin(GPSUARTNUM,GPS_TX_PIN,GPS_RX_PIN,-1,-1) != ESP_OK) {
      myprintf("%s: set pin FAILED %d\n",__func__,ESP_OK);
    }

    init = 0;
  }

  static int i=0;
  static char lbuf[1024];
  int c,ret=0,n=0;
  char dbuf[256];
 retry:  
  uart_get_buffered_data_len(GPSUARTNUM,(size_t*)&n);
  if(n <= 0) return ret;
  n = uart_read_bytes(GPSUARTNUM,dbuf,min(sizeof(dbuf),n),0);
  for(int k=0;k<n;k++) {
    c = (int)dbuf[k];
    if(c == '\r' || c == '\n') {
      lbuf[i] = '\0';
      if(strlen(lbuf) > 0) ret |= gpsbufproc(lbuf);
      i = 0;
    } else {
      if(i < (int)(sizeof(lbuf)-1)) lbuf[i++] = (char)c;
    }
  }
  goto retry;
  
}

static int dogps(int argc,char *argv[])
{
  if(argc > 1) {
    //gpsdouptime = 1;
    //tztestdbg = 1;
    //if(dodiskstuff == NULL) {
    //  char out[80];
    //  sprintf(out,"update");
    //  dodiskstuff = mystrdup(out);
    //}
    int i=atoi(argv[1]);
    if(i > 0) gpsdebug = i;
    return 0;
  }
  if(gpsdebug) gpsdebug = 0; else gpsdebug = 1;
  myprintf("gpsdebug = %d\r\n",gpsdebug);
  return 0;
}

#define DO_ZONEDET
#ifdef DO_ZONEDET
/*
 * Copyright (c) 2018, Bertold Van den Bergh (vandenbergh@bertold.org)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the author nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>

#define __linux__

typedef enum {
    ZD_LOOKUP_IGNORE = -3,
    ZD_LOOKUP_END = -2,
    ZD_LOOKUP_PARSE_ERROR = -1,
    ZD_LOOKUP_NOT_IN_ZONE = 0,
    ZD_LOOKUP_IN_ZONE = 1,
    ZD_LOOKUP_IN_EXCLUDED_ZONE = 2,
    ZD_LOOKUP_ON_BORDER_VERTEX = 3,
    ZD_LOOKUP_ON_BORDER_SEGMENT = 4
} ZDLookupResult;

typedef struct {
    ZDLookupResult lookupResult;

    uint32_t polygonId;
    uint32_t metaId;
    uint8_t numFields;
    char **fieldNames;
    char **data;
} ZoneDetectResult;

struct ZoneDetectOpaque;
typedef struct ZoneDetectOpaque ZoneDetect;

//static ZoneDetect *ZDOpenDatabase(const char *path);
static ZoneDetect *ZDOpenDatabaseFromMemory(void* buffer, size_t length);
static void        ZDCloseDatabase(ZoneDetect *library);

static ZoneDetectResult *ZDLookup(const ZoneDetect *library, float lat, float lon, float *safezone);
static void              ZDFreeResults(ZoneDetectResult *results);


//const char *ZDGetNotice(const ZoneDetect *library);
//uint8_t     ZDGetTableType(const ZoneDetect *library);
//const char *ZDLookupResultToString(ZDLookupResult result);

int         ZDSetErrorHandler(void (*handler)(int, int));
const char *ZDGetErrorString(int errZD);

//float* ZDPolygonToList(const ZoneDetect *library, uint32_t polygonId, size_t* length);

char* ZDHelperSimpleLookupString(const ZoneDetect* library, float lat, float lon);
//static void ZDHelperSimpleLookupStringFree(char* str);

#ifdef FOOP
static void printResults(ZoneDetect *cd, ZoneDetectResult *results, float safezone)
{
    if(!results) {
        myDprintf("No results\n");
        return;
    }

    unsigned int index = 0;
    while(results[index].lookupResult != ZD_LOOKUP_END) {
      //printf("%s:\n", ZDLookupResultToString(results[index].lookupResult));
      //printf("  meta: %u\n", results[index].metaId);
      //printf("  polygon: %u\n", results[index].polygonId);
      
        if(results[index].data) {
            for(unsigned int i = 0; i < results[index].numFields; i++) {
                if(results[index].fieldNames[i] && results[index].data[i]) {
                    myDprintf("  %s: %s\n", results[index].fieldNames[i], results[index].data[i]);
                }
            }
        }
        
        index++;
    }
    ZDFreeResults(results);

    if(index) {
        myDprintf("Safezone: %f\n", safezone);
    }
}

static void onError(int errZD, int errNative)
{
    fprintf(stderr, "ZD error: %s (0x%08X)\n", ZDGetErrorString(errZD), (unsigned)errNative);
}

int main(int argc, char *argv[])
{
    if(argc != 4) {
        myDprintf("Usage: %s dbname lat lon\n", argv[0]);
        return 1;
    }

    ZDSetErrorHandler(onError);

    ZoneDetect *const cd = ZDOpenDatabase(argv[1]);
    if(!cd) return 2;

    const float lat = (float)atof(argv[2]);
    const float lon = (float)atof(argv[3]);

    float safezone = 0;
    ZoneDetectResult *results = ZDLookup(cd, lat, lon, &safezone);
    
    //printResults(cd, results, safezone);

    myDprintf("The simple string is [%s]\n", ZDHelperSimpleLookupString(cd, lat, lon));

    ZDCloseDatabase(cd);

    return 0;
}
#endif // FOOP

static int latlon2tz(char *out,float lat,float lon)
{
  // From https://github.com/nayarsystems/posix_tz_db/tree/master
  // https://github.com/nayarsystems/posix_tz_db/raw/refs/heads/master/zones.csv
  //ZoneDetect *const cd = ZDOpenDatabase(MYMOUNTPT"/TIMEZO~1.BIN");

  if(disable_spi_ops) return -1;
  
  char *fname=MYMOUNTPT"/TZ16.BIN";  // timezone16.bin 1.1Mbytes
  
  struct stat st;
  if(stat(fname,&st)) {
    myDprintf("cannot stat |%s|\n",fname);
    return -1;
  }
  
  //myDprintf("stat |%s| %ld bytes\n",fname,st.st_size);
  char *buffer = mymalloc(st.st_size);
  if(buffer == NULL) {
    myDprintf("cannot allocate %ld byte buffer\n",st.st_size);
    return -1;
  }
  FILE *fp=fopen(fname,"r");
  if(fp == NULL) {
    myDprintf("cannot open |%s|\n",fname);
    myfree(buffer);
    return -1;
  }
  myDprintf("reading |%s| %ld bytes\n",fname,st.st_size);
  fread(buffer,1,st.st_size,fp);
  fclose(fp);
  myDprintf("read %ld bytes\n",st.st_size);
  ZoneDetect *const cd = ZDOpenDatabaseFromMemory(buffer,st.st_size);
  if(!cd) {
    myDprintf("could not open DB\n");
    myfree(buffer);
    return -1;
  }
  myDprintf("DB opened.  Doing lookup..\n");

  float safezone = 0;
  ZoneDetectResult *results=ZDLookup(cd,lat,lon,&safezone); char *zsl=ZDHelperSimpleLookupString(cd,lat,lon);
  strcpy(out,zsl?zsl:"null");
  ZDCloseDatabase(cd);
  myfree(buffer);

  fname = MYMOUNTPT"/ZONES.CSV";
  fp = fopen(fname,"r");
  if(fp == NULL) {
    myDprintf("cannot open |%s|\n",fname);
    if(results) ZDFreeResults(results);
    return -1;
  }
  char buf[128],*args[4];
  while(fgets(buf,sizeof(buf),fp)) {
    lparse(buf,args,4,',');
    if(args[0] && strcmp(args[0],out) == 0) {
      myDprintf("%s = |%s|\n",out,args[1]);
      strcpy(out,args[1]);
      fclose(fp);
      if(results) ZDFreeResults(results);
      return 0;
    }
  }
  fclose(fp);
  if(results) ZDFreeResults(results);
  return -1;
}


/*
 * Copyright (c) 2018, Bertold Van den Bergh (vandenbergh@bertold.org)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the author nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <errno.h>
//#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

enum ZDInternalError {
    ZD_OK,
    ZD_E_DB_OPEN,
    ZD_E_DB_SEEK,
    ZD_E_DB_MMAP,
#if defined(_MSC_VER) || defined(__MINGW32__)
    ZD_E_DB_MMAP_MSVIEW,
    ZD_E_DB_MAP_EXCEPTION,
    ZD_E_DB_MUNMAP_MSVIEW,
#endif
    ZD_E_DB_MUNMAP,
    ZD_E_DB_CLOSE,
    ZD_E_PARSE_HEADER
};

struct ZoneDetectOpaque {
    int fd;
    off_t length;

    uint8_t closeType;
    uint8_t *mapping;

    uint8_t tableType;
    uint8_t version;
    uint8_t precision;
    uint8_t numFields;

    char *notice;
    char **fieldNames;

    uint32_t bboxOffset;
    uint32_t metadataOffset;
    uint32_t dataOffset;
};

static void (*zdErrorHandler)(int, int);
static void zdError(enum ZDInternalError errZD, int errNative)
{
    if (zdErrorHandler) zdErrorHandler((int)errZD, errNative);
}

static int32_t ZDFloatToFixedPoint(float input, float scale, unsigned int precision)
{
    const float inputScaled = input / scale;
    return (int32_t)(inputScaled * (float)(1 << (precision - 1)));
}

#ifdef FOOP
static float ZDFixedPointToFloat(int32_t input, float scale, unsigned int precision)
{
    const float value = (float)input / (float)(1 << (precision - 1));
    return value * scale;
}
#endif // FOOP

static unsigned int ZDDecodeVariableLengthUnsigned(const ZoneDetect *library, uint32_t *index, uint64_t *result)
{
  if(*index >= (uint32_t)library->length) return 0;
  
  uint64_t value = 0;
  unsigned int i = 0;
  uint8_t *const buffer = library->mapping + *index;
  uint8_t *const bufferEnd = library->mapping + library->length - 1;  
  unsigned int shift = 0;
  while(1) {
    value |= ((((uint64_t)buffer[i]) & UINT8_C(0x7F)) << shift);
    shift += 7u;    
    if(!(buffer[i] & UINT8_C(0x80))) break;    
    i++;
    if(buffer + i > bufferEnd) return 0;
  }  
  i++;
  *result = value;
  *index += i;
  return i;
}

static unsigned int ZDDecodeVariableLengthUnsignedReverse(const ZoneDetect *library, uint32_t *index, uint64_t *result)
{
  uint32_t i = *index;

  if(*index >= (uint32_t)library->length) return 0;

  if(library->mapping[i] & UINT8_C(0x80)) return 0;

  if(!i) return 0;

  i--;

  while(library->mapping[i] & UINT8_C(0x80)) {
    if(!i) return 0;
    i--;
  }

  *index = i;

  i++;

  uint32_t i2 = i;
  return ZDDecodeVariableLengthUnsigned(library, &i2, result);
}

static int64_t ZDDecodeUnsignedToSigned(uint64_t value)
{
    return (value & 1) ? -(int64_t)(value / 2) : (int64_t)(value / 2);
}

static unsigned int ZDDecodeVariableLengthSigned(const ZoneDetect *library, uint32_t *index, int32_t *result)
{
    uint64_t value = 0;
    const unsigned int retVal = ZDDecodeVariableLengthUnsigned(library, index, &value);
    *result = (int32_t)ZDDecodeUnsignedToSigned(value);
    return retVal;
}

static char *ZDParseString(const ZoneDetect *library, uint32_t *index)
{
  uint64_t strLength;
  
  if(!ZDDecodeVariableLengthUnsigned(library, index, &strLength)) return NULL;

  uint32_t strOffset = *index;
  unsigned int remoteStr = 0;
  if(strLength >= 256) {
    strOffset = library->metadataOffset + (uint32_t)strLength - 256;
    remoteStr = 1;
    
    if(!ZDDecodeVariableLengthUnsigned(library, &strOffset, &strLength)) return NULL;

    if(strLength > 256) return NULL;
  }

  char *const str = mymalloc((size_t)(strLength + 1));
  
  if(str) {
    size_t i;
    for(i = 0; i < strLength; i++) {
      str[i] = (char)(library->mapping[strOffset + i] ^ UINT8_C(0x80));
    }
    str[strLength] = 0;
  }

  if(!remoteStr) {
    *index += (uint32_t)strLength;
  }
  
  return str;
}

static int ZDParseHeader(ZoneDetect *library)
{
  if(library->length < 7) return -1;

  if(memcmp(library->mapping, "PLB", 3)) return -1;

  library->tableType = library->mapping[3];
  library->version   = library->mapping[4];
  library->precision = library->mapping[5];
  library->numFields = library->mapping[6];
  
  if(library->version >= 2) return -1;

  uint32_t index = UINT32_C(7);
  
  library->fieldNames = mymalloc(library->numFields * sizeof *library->fieldNames);
  if (!library->fieldNames) return -1;

  size_t i;
  for(i = 0; i < library->numFields; i++) {
    library->fieldNames[i] = ZDParseString(library, &index);
  }
  
  library->notice = ZDParseString(library, &index);
  if(!library->notice) return -1;

  uint64_t tmp;
  /* Read section sizes */
  /* By memset: library->bboxOffset = 0 */
  
  if(!ZDDecodeVariableLengthUnsigned(library, &index, &tmp)) return -1;
  library->metadataOffset = (uint32_t)tmp + library->bboxOffset;
  
  if(!ZDDecodeVariableLengthUnsigned(library, &index, &tmp))return -1;
  library->dataOffset = (uint32_t)tmp + library->metadataOffset;
  
  if(!ZDDecodeVariableLengthUnsigned(library, &index, &tmp)) return -1;
  
  /* Add header size to everything */
  library->bboxOffset += index;
  library->metadataOffset += index;
  library->dataOffset += index;
  
  /* Verify file length */
  if(tmp + library->dataOffset != (uint32_t)library->length)  return -2;
  
  return 0;
}

static int ZDPointInBox(int32_t xl, int32_t x, int32_t xr, int32_t yl, int32_t y, int32_t yr)
{
    if((xl <= x && x <= xr) || (xr <= x && x <= xl)) {
        if((yl <= y && y <= yr) || (yr <= y && y <= yl)) {
            return 1;
        }
    }

    return 0;
}

static uint32_t ZDUnshuffle(uint64_t w)
{
    w &=                  0x5555555555555555llu;
    w = (w | (w >> 1))  & 0x3333333333333333llu;
    w = (w | (w >> 2))  & 0x0F0F0F0F0F0F0F0Fllu;
    w = (w | (w >> 4))  & 0x00FF00FF00FF00FFllu;
    w = (w | (w >> 8))  & 0x0000FFFF0000FFFFllu;
    w = (w | (w >> 16)) & 0x00000000FFFFFFFFllu;
    return (uint32_t)w;
}

static void ZDDecodePoint(uint64_t point, int32_t* lat, int32_t* lon)
{
    *lat = (int32_t)ZDDecodeUnsignedToSigned(ZDUnshuffle(point));
    *lon = (int32_t)ZDDecodeUnsignedToSigned(ZDUnshuffle(point >> 1));
}

struct Reader {
    const ZoneDetect *library;
    uint32_t polygonIndex;

    uint64_t numVertices;

    uint8_t done, first;
    uint32_t referenceStart, referenceEnd;
    int32_t referenceDirection;

    int32_t pointLat, pointLon;
    int32_t firstLat, firstLon;
};

static void ZDReaderInit(struct Reader *reader, const ZoneDetect *library, uint32_t polygonIndex)
{
    memset(reader, 0, sizeof(*reader));

    reader->library = library;
    reader->polygonIndex = polygonIndex;

    reader->first = 1;
}

static int ZDReaderGetPoint(struct Reader *reader, int32_t *pointLat, int32_t *pointLon)
{
    int32_t diffLat = 0, diffLon = 0;

readNewPoint:
    if(reader->done > 1) {
        return 0;
    }

    if(reader->first && reader->library->version == 0) {
        if(!ZDDecodeVariableLengthUnsigned(reader->library, &reader->polygonIndex, &reader->numVertices)) return -1;
        if(!reader->numVertices) return -1;
    }

    uint8_t referenceDone = 0;

    if(reader->library->version == 1) {
        uint64_t point = 0;

        if(!reader->referenceDirection) {
            if(!ZDDecodeVariableLengthUnsigned(reader->library, &reader->polygonIndex, &point)) return -1;
        } else {
            if(reader->referenceDirection > 0) {
                /* Read reference forward */
                if(!ZDDecodeVariableLengthUnsigned(reader->library, &reader->referenceStart, &point)) return -1;
                if(reader->referenceStart >= reader->referenceEnd) {
                    referenceDone = 1;
                }
            } else if(reader->referenceDirection < 0) {
                /* Read reference backwards */
                if(!ZDDecodeVariableLengthUnsignedReverse(reader->library, &reader->referenceStart, &point)) return -1;
                if(reader->referenceStart <= reader->referenceEnd) {
                    referenceDone = 1;
                }
            }
        }

        if(!point) {
            /* This is a special marker, it is not allowed in reference mode */
            if(reader->referenceDirection) {
                return -1;
            }

            uint64_t value;
            if(!ZDDecodeVariableLengthUnsigned(reader->library, &reader->polygonIndex, &value)) return -1;

            if(value == 0) {
                reader->done = 2;
            } else if(value == 1) {
                int32_t diff;
                int64_t start;
                if(!ZDDecodeVariableLengthUnsigned(reader->library, &reader->polygonIndex, (uint64_t*)&start)) return -1;
                if(!ZDDecodeVariableLengthSigned(reader->library, &reader->polygonIndex, &diff)) return -1;

                reader->referenceStart = reader->library->dataOffset+(uint32_t)start;
                reader->referenceEnd = reader->library->dataOffset+(uint32_t)(start + diff);
                reader->referenceDirection = diff;
                if(diff < 0) {
                    reader->referenceStart--;
                    reader->referenceEnd--;
                }
                goto readNewPoint;
            }
        } else {
            ZDDecodePoint(point, &diffLat, &diffLon);
            if(reader->referenceDirection < 0) {
                diffLat = -diffLat;
                diffLon = -diffLon;
            }
        }
    }

    if(reader->library->version == 0) {
        if(!ZDDecodeVariableLengthSigned(reader->library, &reader->polygonIndex, &diffLat)) return -1;
        if(!ZDDecodeVariableLengthSigned(reader->library, &reader->polygonIndex, &diffLon)) return -1;
    }

    if(!reader->done) {
        reader->pointLat += diffLat;
        reader->pointLon += diffLon;
        if(reader->first) {
            reader->firstLat = reader->pointLat;
            reader->firstLon = reader->pointLon;
        }
    } else {
        /* Close the polygon (the closing point is not encoded) */
        reader->pointLat = reader->firstLat;
        reader->pointLon = reader->firstLon;
        reader->done = 2;
    }

    reader->first = 0;

    if(reader->library->version == 0) {
        reader->numVertices--;
        if(!reader->numVertices) {
            reader->done = 1;
        }
        if(!diffLat && !diffLon) {
            goto readNewPoint;
        }
    }

    if(referenceDone) {
        reader->referenceDirection = 0;
    }

    if(pointLat) {
        *pointLat = reader->pointLat;
    }

    if(pointLon) {
        *pointLon = reader->pointLon;
    }

    return 1;
}

#ifdef FOOP
static int ZDFindPolygon(const ZoneDetect *library, uint32_t wantedId, uint32_t* metadataIndexPtr, uint32_t* polygonIndexPtr)
{
    uint32_t polygonId = 0;
    uint32_t bboxIndex = library->bboxOffset;

    uint32_t metadataIndex = 0, polygonIndex = 0;

    while(bboxIndex < library->metadataOffset) {
        uint64_t polygonIndexDelta;
        int32_t metadataIndexDelta;
        int32_t tmp;
        if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &tmp)) break;
        if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &tmp)) break;
        if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &tmp)) break;
        if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &tmp)) break;
        if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &metadataIndexDelta)) break;
        if(!ZDDecodeVariableLengthUnsigned(library, &bboxIndex, &polygonIndexDelta)) break;

        metadataIndex += (uint32_t)metadataIndexDelta;
        polygonIndex += (uint32_t)polygonIndexDelta;

        if(polygonId == wantedId) {
            if(metadataIndexPtr) {
                metadataIndex += library->metadataOffset;
                *metadataIndexPtr = metadataIndex;
            }
            if(polygonIndexPtr) {
                polygonIndex += library->dataOffset;
                *polygonIndexPtr = polygonIndex;
            }
            return 1;
        }

        polygonId ++;
    }

    return 0;
}

static int32_t* ZDPolygonToListInternal(const ZoneDetect *library, uint32_t polygonIndex, size_t* length)
{
    struct Reader reader;
    ZDReaderInit(&reader, library, polygonIndex);

    size_t listLength = 2 * 100;
    size_t listIndex = 0;

    int32_t* list = mymalloc(sizeof(int32_t) * listLength);
    if(!list) {
        goto fail;
    }

    while(1) {
        int32_t pointLat, pointLon;
        int result = ZDReaderGetPoint(&reader, &pointLat, &pointLon);
        if(result < 0) {
            goto fail;
        } else if(result == 0) {
            break;
        }

        if(listIndex >= listLength) {
            listLength *= 2;
            if(listLength >= 1048576) {
                goto fail;
            }

            list = myrealloc(list, sizeof(int32_t) * listLength);
            if(!list) {
                goto fail;
            }
        }

        list[listIndex++] = pointLat;
        list[listIndex++] = pointLon;
    }

    if(length) {
        *length = listIndex;
    }

    return list;

fail:
    if(list) {
        myfree(list);
    }
    return NULL;
}

float* ZDPolygonToList(const ZoneDetect *library, uint32_t polygonId, size_t* lengthPtr)
{
    uint32_t polygonIndex;
    int32_t* data = NULL;
    float* flData = NULL;

    if(!ZDFindPolygon(library, polygonId, NULL, &polygonIndex)) {
        goto fail;
    }

    size_t length = 0;
    data = ZDPolygonToListInternal(library, polygonIndex, &length);

    if(!data) {
        goto fail;
    }

    flData = mymalloc(sizeof(float) * length);
    if(!flData) {
        goto fail;
    }

    size_t i;
    for(i = 0; i<length; i+= 2) {
        int32_t lat = data[i];
        int32_t lon = data[i+1];

        flData[i] = ZDFixedPointToFloat(lat, 90, library->precision);
        flData[i+1] = ZDFixedPointToFloat(lon, 180, library->precision);
    }

    if(lengthPtr) {
        *lengthPtr = length;
    }

    return flData;

fail:
    if(data) {
        myfree(data);
    }
    if(flData) {
        myfree(flData);
    }
    return NULL;
}
#endif // FOOP

static ZDLookupResult ZDPointInPolygon(const ZoneDetect *library, uint32_t polygonIndex, int32_t latFixedPoint, int32_t lonFixedPoint, uint64_t *distanceSqrMin)
{
    int32_t pointLat, pointLon, prevLat = 0, prevLon = 0;
    int prevQuadrant = 0, winding = 0;

    uint8_t first = 1;

    struct Reader reader;
    ZDReaderInit(&reader, library, polygonIndex);

    while(1) {
        int result = ZDReaderGetPoint(&reader, &pointLat, &pointLon);
        if(result < 0) {
            return ZD_LOOKUP_PARSE_ERROR;
        } else if(result == 0) {
            break;
        }

        /* Check if point is ON the border */
        if(pointLat == latFixedPoint && pointLon == lonFixedPoint) {
            if(distanceSqrMin) *distanceSqrMin = 0;
            return ZD_LOOKUP_ON_BORDER_VERTEX;
        }

        /* Find quadrant */
        int quadrant;
        if(pointLat >= latFixedPoint) {
            if(pointLon >= lonFixedPoint) {
                quadrant = 0;
            } else {
                quadrant = 1;
            }
        } else {
            if(pointLon >= lonFixedPoint) {
                quadrant = 3;
            } else {
                quadrant = 2;
            }
        }

        if(!first) {
            int windingNeedCompare = 0, lineIsStraight = 0;
            float a = 0, b = 0;

            /* Calculate winding number */
            if(quadrant == prevQuadrant) {
                /* Do nothing */
            } else if(quadrant == (prevQuadrant + 1) % 4) {
                winding ++;
            } else if((quadrant + 1) % 4 == prevQuadrant) {
                winding --;
            } else {
                windingNeedCompare = 1;
            }

            /* Avoid horizontal and vertical lines */
            if((pointLon == prevLon || pointLat == prevLat)) {
                lineIsStraight = 1;
            }

            /* Calculate the parameters of y=ax+b if needed */
            if(!lineIsStraight && (distanceSqrMin || windingNeedCompare)) {
                a = ((float)pointLat - (float)prevLat) / ((float)pointLon - (float)prevLon);
                b = (float)pointLat - a * (float)pointLon;
            }

            int onStraight = ZDPointInBox(pointLat, latFixedPoint, prevLat, pointLon, lonFixedPoint, prevLon);
            if(lineIsStraight && (onStraight || windingNeedCompare)) {
                if(distanceSqrMin) *distanceSqrMin = 0;
                return ZD_LOOKUP_ON_BORDER_SEGMENT;
            }

            /* Jumped two quadrants. */
            if(windingNeedCompare) {
                /* Check if the target is on the border */
                const int32_t intersectLon = (int32_t)(((float)latFixedPoint - b) / a);
                if(intersectLon >= lonFixedPoint-1 && intersectLon <= lonFixedPoint+1) {
                    if(distanceSqrMin) *distanceSqrMin = 0;
                    return ZD_LOOKUP_ON_BORDER_SEGMENT;
                }

                /* Ok, it's not. In which direction did we go round the target? */
                const int sign = (intersectLon < lonFixedPoint) ? 2 : -2;
                if(quadrant == 2 || quadrant == 3) {
                    winding += sign;
                } else {
                    winding -= sign;
                }
            }

            /* Calculate closest point on line (if needed) */
            if(distanceSqrMin) {
                float closestLon, closestLat;
                if(!lineIsStraight) {
                    closestLon = ((float)lonFixedPoint + a * (float)latFixedPoint - a * b) / (a * a + 1);
                    closestLat = (a * ((float)lonFixedPoint + a * (float)latFixedPoint) + b) / (a * a + 1);
                } else {
                    if(pointLon == prevLon) {
                        closestLon = (float)pointLon;
                        closestLat = (float)latFixedPoint;
                    } else {
                        closestLon = (float)lonFixedPoint;
                        closestLat = (float)pointLat;
                    }
                }

                const int closestInBox = ZDPointInBox(pointLon, (int32_t)closestLon, prevLon, pointLat, (int32_t)closestLat, prevLat);

                int64_t diffLat, diffLon;
                if(closestInBox) {
                    /* Calculate squared distance to segment. */
                    diffLat = (int64_t)(closestLat - (float)latFixedPoint);
                    diffLon = (int64_t)(closestLon - (float)lonFixedPoint);
                } else {
                    /*
                     * Calculate squared distance to vertices
                     * It is enough to check the current point since the polygon is closed.
                     */
                    diffLat = (int64_t)(pointLat - latFixedPoint);
                    diffLon = (int64_t)(pointLon - lonFixedPoint);
                }

                /* Note: lon has half scale */
                uint64_t distanceSqr = (uint64_t)(diffLat * diffLat) + (uint64_t)(diffLon * diffLon) * 4;
                if(distanceSqr < *distanceSqrMin) *distanceSqrMin = distanceSqr;
            }
        }

        prevQuadrant = quadrant;
        prevLat = pointLat;
        prevLon = pointLon;
        first = 0;
    };

    if(winding == -4) {
        return ZD_LOOKUP_IN_ZONE;
    } else if(winding == 4) {
        return ZD_LOOKUP_IN_EXCLUDED_ZONE;
    } else if(winding == 0) {
        return ZD_LOOKUP_NOT_IN_ZONE;
    }

    /* Should not happen */
    if(distanceSqrMin) *distanceSqrMin = 0;
    return ZD_LOOKUP_ON_BORDER_SEGMENT;
}

static void ZDCloseDatabase(ZoneDetect *library)
{
  if(library) {
    if(library->fieldNames) {
      size_t i;
      for(i = 0; i < (size_t)library->numFields; i++) {
	if(library->fieldNames[i]) myfree(library->fieldNames[i]);
      }
      myfree(library->fieldNames);
    }
    if(library->notice) myfree(library->notice);
    
    if(library->closeType == 0) {
#ifdef FOOP      
      if(library->mapping && munmap(library->mapping, (size_t)(library->length))) zdError(ZD_E_DB_MUNMAP, 0);
      if(library->fd >= 0 && close(library->fd))                                  zdError(ZD_E_DB_CLOSE, 0);
#endif // FOOP
      
    }    
    myfree(library);
  }
}

static ZoneDetect *ZDOpenDatabaseFromMemory(void* buffer, size_t length)
{
    ZoneDetect *const library = mymalloc(sizeof *library);

    if(library) {
        memset(library, 0, sizeof(*library));
        library->closeType = 1;
        library->length = (long int)length;

        if(library->length <= 0) {
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__APPLE__) || defined(__linux__) || defined(__unix__) || defined(_POSIX_VERSION)
            zdError(ZD_E_DB_SEEK, errno);
#else
            zdError(ZD_E_DB_SEEK, 0);
#endif
            goto fail;
        }

        library->mapping = buffer;

        /* Parse the header */
        if(ZDParseHeader(library)) {
            zdError(ZD_E_PARSE_HEADER, 0);
            goto fail;
        }
    }

    return library;

fail:
    ZDCloseDatabase(library);
    return NULL;
}

#ifdef FOOP
static ZoneDetect *ZDOpenDatabase(const char *path)
{
  ZoneDetect *const library = mymalloc(sizeof *library);

  if(library == NULL) {
    myDprintf("malloc failed\n");
    return NULL;
  }
  memset(library, 0, sizeof(*library));
  library->fd = open(path, O_RDONLY | O_CLOEXEC);
  if(library->fd < 0) {
    myDprintf("open |%s| failed\n",path);
    goto fail;
  }
  library->length = lseek(library->fd, 0, SEEK_END);
  if(library->length <= 0 || library->length > 50331648) {
    myDprintf("lseek end failed\n");
    goto fail;
  }
  lseek(library->fd, 0, SEEK_SET);
  
  library->mapping = mmap(NULL, (size_t)library->length, PROT_READ, MAP_PRIVATE | MAP_FILE, library->fd, 0);
  if(library->mapping == MAP_FAILED) {
    myDprintf("mmap failed\n");
    goto fail;
  }

  /* Parse the header */
  if(ZDParseHeader(library)) {
    myDprintf("parse header failed\n");
    goto fail;
  }
  return library;

fail:
  ZDCloseDatabase(library);
  return NULL;
}
#endif // FOOP

static ZoneDetectResult *ZDLookup(const ZoneDetect *library, float lat, float lon, float *safezone)
{
    const int32_t latFixedPoint = ZDFloatToFixedPoint(lat, 90, library->precision);
    const int32_t lonFixedPoint = ZDFloatToFixedPoint(lon, 180, library->precision);
    size_t numResults = 0;
    uint64_t distanceSqrMin = 0xFFFFFFFFFFFFFFFF; // (uint64_t)-1;
 
    /* Iterate over all polygons */
    uint32_t bboxIndex = library->bboxOffset;
    uint32_t metadataIndex = 0;
    uint32_t polygonIndex = 0;

    ZoneDetectResult *results = mymalloc(sizeof *results);
    if(!results) {
        return NULL;
    }

    uint32_t polygonId = 0;

    while(bboxIndex < library->metadataOffset) {
        int32_t minLat, minLon, maxLat, maxLon, metadataIndexDelta;
        uint64_t polygonIndexDelta;
        if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &minLat)) break;
        if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &minLon)) break;
        if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &maxLat)) break;
        if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &maxLon)) break;
        if(!ZDDecodeVariableLengthSigned(library, &bboxIndex, &metadataIndexDelta)) break;
        if(!ZDDecodeVariableLengthUnsigned(library, &bboxIndex, &polygonIndexDelta)) break;

        metadataIndex += (uint32_t)metadataIndexDelta;
        polygonIndex += (uint32_t)polygonIndexDelta;

        if(latFixedPoint >= minLat) {
            if(latFixedPoint <= maxLat &&
                    lonFixedPoint >= minLon &&
                    lonFixedPoint <= maxLon) {

                const ZDLookupResult lookupResult = ZDPointInPolygon(library, library->dataOffset + polygonIndex, latFixedPoint, lonFixedPoint, (safezone) ? &distanceSqrMin : NULL);
                if(lookupResult == ZD_LOOKUP_PARSE_ERROR) {
                    break;
                } else if(lookupResult != ZD_LOOKUP_NOT_IN_ZONE) {
                    ZoneDetectResult *const newResults = myrealloc(results, sizeof *newResults * (numResults + 2));

                    if(newResults) {
                        results = newResults;
                        results[numResults].polygonId = polygonId;
                        results[numResults].metaId = metadataIndex;
                        results[numResults].numFields = library->numFields;
                        results[numResults].fieldNames = library->fieldNames;
                        results[numResults].lookupResult = lookupResult;

                        numResults++;
                    } else {
		      myfree(results); myDprintf("failed to realloc\n"); return NULL; // break; // return failed
                    }
                }
            }
        } else {
            /* The data is sorted along minLat */
            break;
        }

        polygonId++;
    }

    /* Clean up results */
    size_t i;
    for(i = 0; i < numResults; i++) {
        int insideSum = 0;
        ZDLookupResult overrideResult = ZD_LOOKUP_IGNORE;
        size_t j;
        for(j = i; j < numResults; j++) {
            if(results[i].metaId == results[j].metaId) {
                ZDLookupResult tmpResult = results[j].lookupResult;
                results[j].lookupResult = ZD_LOOKUP_IGNORE;

                /* This is the same result. Is it an exclusion zone? */
                if(tmpResult == ZD_LOOKUP_IN_ZONE) {
                    insideSum++;
                } else if(tmpResult == ZD_LOOKUP_IN_EXCLUDED_ZONE) {
                    insideSum--;
                } else {
                    /* If on the bodrder then the final result is on the border */
                    overrideResult = tmpResult;
                }

            }
        }

        if(overrideResult != ZD_LOOKUP_IGNORE) {
            results[i].lookupResult = overrideResult;
        } else {
            if(insideSum) {
                results[i].lookupResult = ZD_LOOKUP_IN_ZONE;
            }
        }
    }

    /* Remove zones to ignore */
    size_t newNumResults = 0;
    for(i = 0; i < numResults; i++) {
        if(results[i].lookupResult != ZD_LOOKUP_IGNORE) {
            results[newNumResults] = results[i];
            newNumResults++;
        }
    }
    numResults = newNumResults;

    /* Lookup metadata */
    for(i = 0; i < numResults; i++) {
        uint32_t tmpIndex = library->metadataOffset + results[i].metaId;
        results[i].data = mymalloc(library->numFields * sizeof *results[i].data);
        if(results[i].data) {
            size_t j;
            for(j = 0; j < library->numFields; j++) {
                results[i].data[j] = ZDParseString(library, &tmpIndex);
                if (!results[i].data[j]) {
                    /* free all allocated memory */
                    size_t m;
                    for(m = 0; m < j; m++) {
                        if(results[i].data[m]) {
                            myfree(results[i].data[m]);
                        }
                    }
                    size_t k;
                    for(k = 0; k < i; k++) {
                        size_t l;
                        for(l = 0; l < (size_t)results[k].numFields; l++) {
                            if(results[k].data[l]) {
                                myfree(results[k].data[l]);
                            }
                        }
                        if (results[k].data) {
                            myfree(results[k].data);
                        }
                    }
                    myfree(results);
                    return NULL;
                }
            }
        }
        else {
            /* free all allocated memory */
            size_t k;
            for(k = 0; k < i; k++) {
                size_t l;
                for(l = 0; l < (size_t)results[k].numFields; l++) {
                    if(results[k].data[l]) {
                        myfree(results[k].data[l]);
                    }
                }
                if (results[k].data) {
                    myfree(results[k].data);
                }
            }
            myfree(results);
            return NULL;
        }
    }

    /* Write end marker */
    results[numResults].lookupResult = ZD_LOOKUP_END;
    results[numResults].numFields = 0;
    results[numResults].fieldNames = NULL;
    results[numResults].data = NULL;

    if(safezone) {
        *safezone = sqrtf((float)distanceSqrMin) * 90 / (float)(1 << (library->precision - 1));
    }

    return results;
}

static void ZDFreeResults(ZoneDetectResult *results)
{
    unsigned int index = 0;

    if(!results) {
        return;
    }

    while(results[index].lookupResult != ZD_LOOKUP_END) {
        if(results[index].data) {
            size_t i;
            for(i = 0; i < (size_t)results[index].numFields; i++) {
                if(results[index].data[i]) {
                    myfree(results[index].data[i]);
                }
            }
            myfree(results[index].data);
        }
        index++;
    }
    myfree(results);
}

#ifdef FOOP
const char *ZDGetNotice(const ZoneDetect *library)
{
    return library->notice;
}

uint8_t ZDGetTableType(const ZoneDetect *library)
{
    return library->tableType;
}

const char *ZDLookupResultToString(ZDLookupResult result)
{
    switch(result) {
        case ZD_LOOKUP_IGNORE:
            return "Ignore";
        case ZD_LOOKUP_END:
            return "End";
        case ZD_LOOKUP_PARSE_ERROR:
            return "Parsing error";
        case ZD_LOOKUP_NOT_IN_ZONE:
            return "Not in zone";
        case ZD_LOOKUP_IN_ZONE:
            return "In zone";
        case ZD_LOOKUP_IN_EXCLUDED_ZONE:
            return "In excluded zone";
        case ZD_LOOKUP_ON_BORDER_VERTEX:
            return "Target point is border vertex";
        case ZD_LOOKUP_ON_BORDER_SEGMENT:
            return "Target point is on border";
    }

    return "Unknown";
}
#endif // FOOP

#define ZD_E_COULD_NOT(msg) "could not " msg

const char *ZDGetErrorString(int errZD)
{
    switch ((enum ZDInternalError)errZD) {
        default:
            assert(0);
        case ZD_OK                :
            return "";
        case ZD_E_DB_OPEN         :
            return ZD_E_COULD_NOT("open database file");
        case ZD_E_DB_SEEK         :
            return ZD_E_COULD_NOT("retrieve database file size");
        case ZD_E_DB_MMAP         :
            return ZD_E_COULD_NOT("map database file to system memory");
#if defined(_MSC_VER) || defined(__MINGW32__)
        case ZD_E_DB_MMAP_MSVIEW  :
            return ZD_E_COULD_NOT("open database file view");
        case ZD_E_DB_MAP_EXCEPTION:
            return "I/O exception occurred while accessing database file view";
        case ZD_E_DB_MUNMAP_MSVIEW:
            return ZD_E_COULD_NOT("close database file view");
#endif
        case ZD_E_DB_MUNMAP       :
            return ZD_E_COULD_NOT("unmap database");
        case ZD_E_DB_CLOSE        :
            return ZD_E_COULD_NOT("close database file");
        case ZD_E_PARSE_HEADER    :
            return ZD_E_COULD_NOT("parse database header");
    }
}

#undef ZD_E_COULD_NOT

int ZDSetErrorHandler(void (*handler)(int, int))
{
    zdErrorHandler = handler;
    return 0;
}

char* ZDHelperSimpleLookupString(const ZoneDetect* library, float lat, float lon)
{
    ZoneDetectResult *result = ZDLookup(library, lat, lon, NULL);
    if(!result) {
        return NULL;
    }

    char* output = NULL;

    if(result[0].lookupResult == ZD_LOOKUP_END) {
        goto done;
    }

    char* strings[2] = {NULL};

    unsigned int i;
    for(i = 0; i < result[0].numFields; i++) {
        if(result[0].fieldNames[i] && result[0].data[i]) {
            if(library->tableType == 'T') {
                if(!strcmp(result[0].fieldNames[i], "TimezoneIdPrefix")) {
                    strings[0] = result[0].data[i];
                }
                if(!strcmp(result[0].fieldNames[i], "TimezoneId")) {
                    strings[1] = result[0].data[i];
                }
            }
            if(library->tableType == 'C') {
                if(!strcmp(result[0].fieldNames[i], "Name")) {
                    strings[0] = result[0].data[i];
                }
            }
        }
    }

    size_t length = 0;
    for(i=0; i<sizeof(strings)/sizeof(char*); i++) {
        if(strings[i]) {
            size_t partLength = strlen(strings[i]);
            if(partLength > 512) {
                goto done;
            }
            length += partLength;
        }
    }

    if(length == 0) {
        goto done;
    }

    length += 1;

    output = (char*)mymalloc(length);
    if(output) {
        output[0] = 0;
        for(i=0; i<sizeof(strings)/sizeof(char*); i++) {
            if(strings[i]) {
#if defined(_MSC_VER)
                strcat_s(output + strlen(output), length-strlen(output), strings[i]);
#else
                strcat(output + strlen(output), strings[i]);
#endif
            }
        }
    }

done:
    ZDFreeResults(result);
    return output;
}

#ifdef FOOP
static void ZDHelperSimpleLookupStringFree(char* str)
{
    myfree(str);
}
#endif // FOOP
/*end copyright*/
#endif // DO_ZONEDET

#endif // DO_GPS

/*
 * Copyright notice:
 * SPDX-FileCopyrightText: Copyright (c) 2025-2026 Richard Lamb
 * SPDX-License-Identifier: CC BY-NC-SA-4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
 * Author: RHLamb (W3TOY) wwvb@xtcn.com
 *
 * The above copyright notice shall be included in all copies or substantial portions of this software.
 */
static void periodic_ui(void)
{
  static uint8_t init=1;
  static uint8_t lastwwvbledst=0;
  static time_t lasttime=0;
  static int lastyday=370;
  static char lasttz[32]={0};
  char out[128],curtz[32];

  int64_t tt=esp_timer_get_time();

  if(consoleok) console_proc();
  rlprintf_proc(); // calls slow lcdconsole()
  
  if(wwvbinitialized == 0) return;
  
  if(init) {
     create_ui(0);
     init = 0;
  }
  
#ifdef DO_GPS
  gpsproc(); // MUST be done more frequently than 1 sec
#endif // DO_GPS

  // only evey second
  curtime = time(NULL); // curtime used by other tasks too
  if(curtime == lasttime) {
    uidelayms = (0.999*uidelayms) + (0.001* ((esp_timer_get_time()-tt)/1000) );
    return;
  }
  lasttime = curtime;

  // rest called evey second  
  if(ext_led_state != lastwwvbledst) {
    if(ext_led_state) lv_led_on(wwvbled); else lv_led_off(wwvbled);
    lastwwvbledst = ext_led_state;
  }

  struct tm _tm,*t;
  t = &_tm;
  time_t _tt=curtime;
  memcpy(t,localtime(&_tt),sizeof(struct tm));
  showwwvb(0,t);
  updateanalog(t);
  do7segup(t->tm_hour,t->tm_min,t->tm_sec);
  strftime(curtz,sizeof(curtz),"%Z",t);      
  if(t->tm_yday != lastyday || strcmp(curtz,lasttz)) {
    strftime(out,sizeof(out),"%a %b %e, %Y %Z",t);
    lv_label_set_text(datelabel,out);       
    myDprintf("datefix |%s| %s %s\n",out,t->tm_yday != lastyday?"day+":"",strcmp(curtz,lasttz)?"tz+":"");
    lastyday = t->tm_yday;
    strcpy(lasttz,curtz);
  }

  // note: we recive the packet 1 min after the actual time // fixsync
  if(timehasbeenset && gotgoodone > 0 &&
     !(t->tm_min >= 10 && t->tm_min < 16) &&
     !(t->tm_min >= 40 && t->tm_min < 46) ) gotgoodone--;
  
#ifdef DO_GPS
  gpsdisp(); // may show wwvb bearing if lat/lon set
#endif // DO_GPS      

#ifdef DO_WIFI
  if(haveinet) dontp(1,NULL);
#endif // DO_WIFI
  
  // housekeeping
  static time_t lastup=0;
  if(timehasbeenset) {
    if(lastup == 0) lastup = curtime;
    // every 8 hrs
    if((int)(curtime - lastup) > (86400/3) && dodiskstuff == NULL) {
      dodiskstuff = mystrdup("update,log");
      lastup = curtime;
    }
  }
  if(dodiskstuff) { // must do here. cannot switch tasks
    char *args[10];
    int n = lparse(dodiskstuff,args,10,',');
    for(int i=0;i<n;i++) {
      myDprintf("|%s|\n",args[i]);
      if(strcmp(args[i],"load") == 0) configload();
      else if(strcmp(args[i],"update") == 0) configupdate();
      else if(strcmp(args[i],"log") == 0) log2disk();
      else { }
    }
    myfree(dodiskstuff);
    dodiskstuff = NULL;
    lastup = curtime;
  }
  
  uidelayms = (0.999*uidelayms) + (0.001* ((esp_timer_get_time()-tt)/1000) );
  
}

static void myuitask(void *pvParameter)
{
  while(1) {
    if(disable_spi_ops) { // lv_timer_handler() does SPI calls
      vTaskDelay(5);
      continue;
    }    
    //xSemaphoreTake(lvgl_mux,portMAX_DELAY); // all lvgl happens below. so no need
    periodic_ui();
    //xSemaphoreGive(lvgl_mux);
    int64_t tt=esp_timer_get_time();
    lv_timer_handler();
    lvdelayms = (0.999*lvdelayms) + (0.001* ((esp_timer_get_time()-tt)/1000) ); // avg 24ms
    vTaskDelay(5); // 100hz 10ms/tick
  }
}

static void init_display(void);
void app_main(void)
{
  resetreason = esp_reset_reason();
  mysetupwtchdog(10);
  rlprintfmux = xSemaphoreCreateMutex();
  console_proc(); // inits console
  if(resetreason != ESP_RST_DEEPSLEEP) { // (re)init long term vars
    mystarttime = esp_timer_get_time();
  }
  fatinit(); // init and mount file system
  configload(); // load config from fatfs
  mysleep(2);
  myi2cinit();
    
  init_display();
#ifdef CONFIG_IDF_TARGET_ESP32S2  
  xTaskCreate(myuitask,"uitask",4096,NULL,(tskIDLE_PRIORITY+1),NULL);
#endif
#ifdef CONFIG_IDF_TARGET_ESP32S3
  //xTaskCreatePinnedToCore(myuitask,"uitask",4096,NULL,0,NULL,1);
  xTaskCreatePinnedToCore(myuitask,"uitask",4096,NULL,(tskIDLE_PRIORITY+1),NULL,1);
#endif  
  
  mysleep(2);
  dofreq(1,NULL); // set up si5351 for LO and ADC
#ifdef CONFIG_IDF_TARGET_ESP32S2
  xTaskCreate(
	      (TaskFunction_t)wwvbtask,"wwvbtask",
	      16000/sizeof(configSTACK_DEPTH_TYPE),
	      NULL,(tskIDLE_PRIORITY+1),NULL);
#endif
#ifdef CONFIG_IDF_TARGET_ESP32S3  
  xTaskCreatePinnedToCore(
			  (TaskFunction_t)wwvbtask,"wwvbtask",
			  16000/sizeof(configSTACK_DEPTH_TYPE),
			  NULL,(tskIDLE_PRIORITY+1),NULL,0); // CPU0
#endif

  mysleep(2); char *dfw[]={"","disk",NULL}; dofwupdate(2,dfw); // check for new fw on disk
  
  while(1) {
    mywatchdogyield();
    //if(consoleok) console_proc();
    //rlprintf_proc();
    //vTaskDelay(10);
    vTaskDelay(pdMS_TO_TICKS(1000));
    
  }
  
}
static void app_end(void)
{
}


#ifdef CONFIG_IDF_TARGET_ESP32S2

#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_ili9488.h>

#define CONFIG_TFT_BACKLIGHT_PIN GPIO_NUM_36 // "LED" H8
#define CONFIG_SPI_MOSI          GPIO_NUM_34 // "T_DIN+SDI" H12/6
#define CONFIG_SPI_MISO          GPIO_NUM_33 // "T_DO" H13
#define CONFIG_SPI_CLOCK         GPIO_NUM_37 // "T_CLK+SCK" H10/7
#define CONFIG_TFT_CS_PIN        GPIO_NUM_40 // (MTDO-pullup) "CS" H3
#define CONFIG_TFT_DC_PIN        GPIO_NUM_38 // "DC" (STRAPPING-pulldown) H5
#define CONFIG_TFT_RESET_PIN     GPIO_NUM_39 // (MTCK) "RESET" H4
#define TOUCH_CS_PIN             GPIO_NUM_35 // "T_CS" H11

/*
 * SPDX-FileCopyrightText: 2022 atanisoft (github.com/atanisoft)
 *
 * SPDX-License-Identifier: MIT
 */
// LVGL is NOT thread safe!  So do it all in same thread/task.
// Uncomment the following line to enable using double buffering of LVGL color data.
#define USE_DOUBLE_BUFFERING 1
#define CONFIG_DISPLAY_COLOR_MODE ESP_LCD_COLOR_SPACE_RGB
#define LVGL_UPDATE_PERIOD_MS 5  // for lvgl timer tick
static const int DISPLAY_HORIZONTAL_PIXELS = 480;
static const int DISPLAY_VERTICAL_PIXELS = 320;
static const int DISPLAY_COMMAND_BITS = 8;
static const int DISPLAY_PARAMETER_BITS = 8;
static const unsigned int DISPLAY_REFRESH_HZ = 40000000;
static const int DISPLAY_SPI_QUEUE_LEN = 10;
static const int SPI_MAX_TRANSFER_SIZE = 32768;
static const size_t LV_BUFFER_SIZE = DISPLAY_HORIZONTAL_PIXELS*25; // Default to 25 lines of color data
static esp_lcd_panel_io_handle_t lcd_io_handle = NULL;
static esp_lcd_panel_handle_t lcd_handle = NULL;
static lv_disp_draw_buf_t lv_disp_buf;
static lv_disp_drv_t lv_disp_drv;
static lv_disp_t *lv_display = NULL;
static lv_color_t *lv_buf_1 = NULL;
static lv_color_t *lv_buf_2 = NULL;

static bool notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io,esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
  lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
  lv_disp_flush_ready(disp_driver);
  return false;
}
static void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
  esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
  int offsetx1 = area->x1;
  int offsetx2 = area->x2;
  int offsety1 = area->y1;
  int offsety2 = area->y2;
  esp_lcd_panel_draw_bitmap(panel_handle,offsetx1,offsety1,offsetx2+1,offsety2+1,color_map);
}
static void IRAM_ATTR lvgl_tick_cb(void *param)
{
  lv_tick_inc(LVGL_UPDATE_PERIOD_MS);
}

static int display_brightness_init(void)
{
  const ledc_channel_config_t LCD_backlight_channel = {
    .gpio_num = (gpio_num_t)CONFIG_TFT_BACKLIGHT_PIN,
    .speed_mode = LEDC_LOW_SPEED_MODE,
    .channel = LEDC_CHANNEL_0,
    .intr_type = LEDC_INTR_DISABLE,
    .timer_sel = LEDC_TIMER_1,
    .duty = 0,
    .hpoint = 0,
    .flags = {
      .output_invert = 0
    }
  };
  const ledc_timer_config_t LCD_backlight_timer = {
    .speed_mode = LEDC_LOW_SPEED_MODE,
    .duty_resolution = LEDC_TIMER_10_BIT,
    .timer_num = LEDC_TIMER_1,
    .freq_hz = LEDC_TIMER_1_FREQ, // check any other timer users
    .clk_cfg = LEDC_AUTO_CLK
  };
  myDprintf("Initializing LEDC for backlight pin: %d\n",CONFIG_TFT_BACKLIGHT_PIN);
  esp_err_t ret;
  if((ret=ledc_timer_config(&LCD_backlight_timer)) != ESP_OK) goto err;
  if((ret=ledc_channel_config(&LCD_backlight_channel)) != ESP_OK) goto err;
  return 0;
 err:
  myDprintf("failed %s\n",esp_err_to_name(ret));
  return -1;
}
static void display_brightness_set(int brightness_percentage)
{
  if(brightness_percentage > 100) brightness_percentage = 100;
  else if(brightness_percentage < 0) brightness_percentage = 0;
  else { }
  myDprintf("Setting backlight to %d%%\n",brightness_percentage);
  // LEDC resolution set to 10bits, thus: 100% = 1023
  uint32_t duty_cycle = (1023*brightness_percentage)/100;
  ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0,duty_cycle));
  ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE,LEDC_CHANNEL_0));
}

static int initialize_spi(void)
{
  myDprintf("Initializing SPI bus (MOSI:%d, MISO:%d, CLK:%d)\n",
           CONFIG_SPI_MOSI, CONFIG_SPI_MISO, CONFIG_SPI_CLOCK);
  spi_bus_config_t bus = {
    .mosi_io_num = CONFIG_SPI_MOSI,
    .miso_io_num = CONFIG_SPI_MISO,
    .sclk_io_num = CONFIG_SPI_CLOCK,
    .quadwp_io_num = GPIO_NUM_NC,
    .quadhd_io_num = GPIO_NUM_NC,
    .data4_io_num = GPIO_NUM_NC,
    .data5_io_num = GPIO_NUM_NC,
    .data6_io_num = GPIO_NUM_NC,
    .data7_io_num = GPIO_NUM_NC,
    .max_transfer_sz = SPI_MAX_TRANSFER_SIZE,
    .flags = SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MISO |
    SPICOMMON_BUSFLAG_MOSI | SPICOMMON_BUSFLAG_MASTER,
    .intr_flags = ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM
  };
  esp_err_t ret=spi_bus_initialize(SPI2_HOST, &bus, SPI_DMA_CH_AUTO);
  if(ret != ESP_OK) {
    myDprintf("failed %s\n",esp_err_to_name(ret));
    return -1;
  }
  return 0;
}
static int initialize_display(void)
{
  const esp_lcd_panel_io_spi_config_t io_config = {
    .cs_gpio_num = CONFIG_TFT_CS_PIN,
    .dc_gpio_num = CONFIG_TFT_DC_PIN,
    .spi_mode = 0,
    .pclk_hz = DISPLAY_REFRESH_HZ,
    .trans_queue_depth = DISPLAY_SPI_QUEUE_LEN,
    .on_color_trans_done = notify_lvgl_flush_ready,
    .user_ctx = &lv_disp_drv,
    .lcd_cmd_bits = DISPLAY_COMMAND_BITS,
    .lcd_param_bits = DISPLAY_PARAMETER_BITS,
    .flags = {
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
      .dc_as_cmd_phase = 0,
      .dc_low_on_data = 0,
      .octal_mode = 0,
      .lsb_first = 0
#else
      .dc_low_on_data = 0,
      .octal_mode = 0,
      .sio_mode = 0,
      .lsb_first = 0,
      .cs_high_active = 0
#endif
    }
  };
  const esp_lcd_panel_dev_config_t lcd_config = {
    .reset_gpio_num = CONFIG_TFT_RESET_PIN,
    .color_space = CONFIG_DISPLAY_COLOR_MODE,
    .bits_per_pixel = 18,
    .flags = {
      .reset_active_high = 0
    },
    .vendor_config = NULL
  };
  esp_err_t ret;
  if((ret=esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI2_HOST,&io_config,&lcd_io_handle)) != ESP_OK) goto err;
  if((ret=esp_lcd_new_panel_ili9488(lcd_io_handle, &lcd_config,LV_BUFFER_SIZE,&lcd_handle)) != ESP_OK) goto err;
  if((ret=esp_lcd_panel_reset(lcd_handle)) != ESP_OK) goto err;
  if((ret=esp_lcd_panel_init(lcd_handle)) != ESP_OK) goto err;
  if((ret=esp_lcd_panel_invert_color(lcd_handle,false)) != ESP_OK) goto err;
  //esp_lcd_panel_swap_xy(lcd_handle,false);
  esp_lcd_panel_swap_xy(lcd_handle,true);  
  if(rotatedisp) {
    if((ret=esp_lcd_panel_mirror(lcd_handle,false,true)) != ESP_OK) goto err;
  } else {
    if((ret=esp_lcd_panel_mirror(lcd_handle,true,false)) != ESP_OK) goto err;
  }
  if((ret=esp_lcd_panel_set_gap(lcd_handle,0,0)) != ESP_OK) goto err;
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5,0,0)
  if((ret=esp_lcd_panel_disp_off(lcd_handle,false)) != ESP_OK) goto err;
#else
  if((ret=esp_lcd_panel_disp_on_off(lcd_handle,true)) != ESP_OK) goto err;
#endif
  return 0;
 err:  
  myDprintf("failed %s\n",esp_err_to_name(ret));
  return -1;
}
static int initialize_lvgl(void)
{
  myDprintf("Initializing LVGL\n");
  lv_init();
  myDprintf("Allocating %zu bytes for LVGL buffer\n",LV_BUFFER_SIZE*sizeof(lv_color_t));
  lv_buf_1 = (lv_color_t *)heap_caps_malloc(LV_BUFFER_SIZE*sizeof(lv_color_t),MALLOC_CAP_DMA);
#if USE_DOUBLE_BUFFERING
  myDprintf("Allocating %zu bytes for second LVGL buffer\n",LV_BUFFER_SIZE*sizeof(lv_color_t));
  lv_buf_2 = (lv_color_t *)heap_caps_malloc(LV_BUFFER_SIZE*sizeof(lv_color_t),MALLOC_CAP_DMA);
#endif
  myDprintf("Creating LVLG display buffer\n");
  lv_disp_draw_buf_init(&lv_disp_buf,lv_buf_1,lv_buf_2,LV_BUFFER_SIZE);
  
  myDprintf("Initializing %dx%d display\n",DISPLAY_HORIZONTAL_PIXELS,DISPLAY_VERTICAL_PIXELS);
  lv_disp_drv_init(&lv_disp_drv);
  lv_disp_drv.hor_res = DISPLAY_HORIZONTAL_PIXELS;
  lv_disp_drv.ver_res = DISPLAY_VERTICAL_PIXELS;
  lv_disp_drv.flush_cb = lvgl_flush_cb;
  lv_disp_drv.draw_buf = &lv_disp_buf;
  lv_disp_drv.user_data = lcd_handle;
  lv_display = lv_disp_drv_register(&lv_disp_drv);
  
  myDprintf("Creating LVGL tick timer\n");
  const esp_timer_create_args_t lvgl_tick_timer_args = {
    .callback = &lvgl_tick_cb,
    .name = "lvgl_tick"
  };
  esp_err_t ret;
  esp_timer_handle_t lvgl_tick_timer = NULL;
  if((ret=esp_timer_create(&lvgl_tick_timer_args,&lvgl_tick_timer)) != ESP_OK) goto err;
  if((ret=esp_timer_start_periodic(lvgl_tick_timer,LVGL_UPDATE_PERIOD_MS*1000)) != ESP_OK) goto err;
  return 0;
 err:
  myDprintf("failed %s\n",esp_err_to_name(ret));
  return -1;
}
#include <esp_lcd_touch_xpt2046.h>
static esp_lcd_touch_handle_t tp = NULL;
static esp_lcd_panel_io_handle_t tp_io_handle = NULL;
static esp_lcd_panel_io_spi_config_t tp_io_config = ESP_LCD_TOUCH_IO_SPI_XPT2046_CONFIG(TOUCH_CS_PIN);
static lv_indev_drv_t indev_drv;
static void touch_driver_read(lv_indev_drv_t *drv,lv_indev_data_t *data)
{
  uint8_t count = 0;
  // Update touch point data.
  ESP_ERROR_CHECK(esp_lcd_touch_read_data(tp));
  
  data->state = LV_INDEV_STATE_REL;

#ifdef FOOP
  uint16_t x[1];
  uint16_t y[1];
  uint16_t strength[1];
  if(esp_lcd_touch_get_coordinates(tp,x,y,strength,&count,1)) {
    data->point.x = x[0];
    data->point.y = y[0];
    data->state = LV_INDEV_STATE_PR;
    //printf("%s: %d %d\n",__func__,data->point.x,data->point.y);
  }
#endif // FOOP
  esp_lcd_touch_point_data_t touch_point_data[1];
  if(esp_lcd_touch_get_data(tp,touch_point_data,&count,1)) {
    data->point.x = touch_point_data[0].x;
    data->point.y = touch_point_data[0].y;
    data->state = LV_INDEV_STATE_PR;
  }

  
  data->continue_reading = false;
}
static int initialize_input(void)
{
  esp_err_t ret;
  ret = esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI2_HOST,&tp_io_config,&tp_io_handle);
  if(ret != ESP_OK) goto err;
  esp_lcd_touch_config_t tp_cfg = {
    .x_max = DISPLAY_VERTICAL_PIXELS,  // flipped
    //.y_max = DISPLAY_HORIZONTAL_PIXELS,
    .y_max = (100*DISPLAY_HORIZONTAL_PIXELS)/95,  // touch panel doesnt match lcd so adjust
    .rst_gpio_num = -1,
    .int_gpio_num = -1,
    .flags = {
      .swap_xy = 1,  // and flipped
      .mirror_x = 1,
      .mirror_y = 1,
    },
  };
  if(rotatedisp) {
    tp_cfg.flags.mirror_x = 0;
    tp_cfg.flags.mirror_y = 0;
  }
  
  myDprintf("Initialize touch controller XPT2046\n");
  ret = esp_lcd_touch_new_spi_xpt2046(tp_io_handle,&tp_cfg,&tp);
  if(ret != ESP_OK) goto err;  
  lv_indev_drv_init( &indev_drv );
  indev_drv.type = LV_INDEV_TYPE_POINTER;
  indev_drv.read_cb = touch_driver_read;
  lv_indev_drv_register( &indev_drv );
  return 0;
 err:
  myDprintf("failed %s\n",esp_err_to_name(ret));
  return -1;
}

static void init_display(void)
{
  if(display_brightness_init()) return;
  display_brightness_set(0);
  if(initialize_spi()) return;
  if(initialize_display()) return;
  if(initialize_lvgl()) return;
  display_brightness_set(70);
  if(initialize_input()) return;
  display_inited = 1;
}
/*end copyright*/
#endif // CONFIG_IDF_TARGET_ESP32S2

#ifdef CONFIG_IDF_TARGET_ESP32S3
/******************************************************************
 * LCD/Touch stuff
 *copyright*
 * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
 * SPDX-License-Identifier: Apache-2.0
 * See: https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/api-reference/peripherals/lcd/rgb_lcd.html
 *****************************************************************/

#define PCA9557_I2C_ADDRESS 0x18
#define PCA9557_INPUT_PORT_REG    0x00
#define PCA9557_OUTPUT_PORT_REG   0x01
#define PCA9557_POLARITY_INV_REG  0x02
#define PCA9557_CONFIG_REG        0x03
#define PCA9557_INPUT  1
#define PCA9557_OUTPUT 0
typedef struct pca9557_dev_t { i2c_master_dev_handle_t i2c_dev; } pca9557_dev_t;
typedef struct pca9557_dev_t *pca9557_handle_t;

/*
 * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define CONFIG_ESP_LCD_TOUCH_MAX_POINTS 5
#define CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS 1
  
/**
 * @file
 * @brief ESP LCD touch
 */


#include <stdbool.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "driver/gpio.h"
#include "esp_lcd_panel_io.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"


/**
 * @brief Touch controller type
 *
 */
typedef struct esp_lcd_touch_s esp_lcd_touch_t;
typedef esp_lcd_touch_t *esp_lcd_touch_handle_t;

/**
 * @brief Touch controller interrupt callback type
 *
 */
typedef void (*esp_lcd_touch_interrupt_callback_t)(esp_lcd_touch_handle_t tp);

/**
 * @brief Touch Configuration Type
 *
 */
typedef struct {
  uint16_t x_max; /*!< X coordinates max (for mirroring) */
  uint16_t y_max; /*!< Y coordinates max (for mirroring) */
  
  gpio_num_t rst_gpio_num;    /*!< GPIO number of reset pin */
  gpio_num_t int_gpio_num;    /*!< GPIO number of interrupt pin */
  
  struct {
    unsigned int reset: 1;    /*!< Level of reset pin in reset */
    unsigned int interrupt: 1;/*!< Active Level of interrupt pin */
  } levels;
  
  struct {
    unsigned int swap_xy: 1;  /*!< Swap X and Y after read coordinates */
    unsigned int mirror_x: 1; /*!< Mirror X after read coordinates */
    unsigned int mirror_y: 1; /*!< Mirror Y after read coordinates */
  } flags;
  
  /*!< User callback called after get coordinates from touch controller for apply user adjusting */
  void (*process_coordinates)(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num);
  /*!< User callback called after the touch interrupt occurred */
  esp_lcd_touch_interrupt_callback_t interrupt_callback;
  /*!< User data passed to callback */
  void *user_data;
  /*!< User data passed to driver */
  void *driver_data;
} esp_lcd_touch_config_t;

typedef struct {
  uint8_t points; /*!< Count of touch points saved */
  
  struct {
    uint16_t x; /*!< X coordinate */
    uint16_t y; /*!< Y coordinate */
    uint16_t strength; /*!< Strength */
  } coords[CONFIG_ESP_LCD_TOUCH_MAX_POINTS];
  
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
  uint8_t buttons; /*!< Count of buttons states saved */
  
  struct {
    uint8_t status; /*!< Status of button */
  } button[CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS];
#endif
  
  portMUX_TYPE lock; /*!< Lock for read/write */
} esp_lcd_touch_data_t;

/**
 * @brief Declare of Touch Type
 *
 */
struct esp_lcd_touch_s {
  
    /**
     * @brief set touch controller into sleep mode
     *
     * @note This function is usually blocking.
     *
     * @param tp: Touch handler
     *
     * @return
     *      - ESP_OK on success, otherwise returns ESP_ERR_xxx
     */
    esp_err_t (*enter_sleep)(esp_lcd_touch_handle_t tp);

    /**
     * @brief set touch controller into normal mode
     *
     * @note This function is usually blocking.
     *
     * @param tp: Touch handler
     *
     * @return
     *      - ESP_OK on success, otherwise returns ESP_ERR_xxx
     */
    esp_err_t (*exit_sleep)(esp_lcd_touch_handle_t tp);

    /**
     * @brief Read data from touch controller (mandatory)
     *
     * @note This function is usually blocking.
     *
     * @param tp: Touch handler
     *
     * @return
     *      - ESP_OK on success, otherwise returns ESP_ERR_xxx
     */
    esp_err_t (*read_data)(esp_lcd_touch_handle_t tp);

    /**
     * @brief Get coordinates from touch controller (mandatory)
     *
     * @param tp: Touch handler
     * @param x: Array of X coordinates
     * @param y: Array of Y coordinates
     * @param strength: Array of strengths
     * @param point_num: Count of points touched (equals with count of items in x and y array)
     * @param max_point_num: Maximum count of touched points to return (equals with max size of x and y array)
     *
     * @return
     *      - Returns true, when touched and coordinates readed. Otherwise returns false.
     */
    bool (*get_xy)(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num);


#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
    /**
     * @brief Get button state (optional)
     *
     * @param tp: Touch handler
     * @param n: Button index
     * @param state: Button state
     *
     * @return
     *      - Returns true, when touched and coordinates readed. Otherwise returns false.
     */
    esp_err_t (*get_button_state)(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state);
#endif

    /**
     * @brief Swap X and Y after read coordinates (optional)
     *        If set, then not used SW swapping.
     *
     * @param tp: Touch handler
     * @param swap: Set swap value
     *
     * @return
     *      - ESP_OK on success, otherwise returns ESP_ERR_xxx
     */
    esp_err_t (*set_swap_xy)(esp_lcd_touch_handle_t tp, bool swap);

    /**
     * @brief Are X and Y coordinates swapped (optional)
     *
     * @param tp: Touch handler
     * @param swap: Get swap value
     *
     * @return
     *      - ESP_OK on success, otherwise returns ESP_ERR_xxx
     */
    esp_err_t (*get_swap_xy)(esp_lcd_touch_handle_t tp, bool *swap);

    /**
     * @brief Mirror X after read coordinates
     *        If set, then not used SW mirroring.
     *
     * @param tp: Touch handler
     * @param mirror: Set X mirror value
     *
     * @return
     *      - ESP_OK on success, otherwise returns ESP_ERR_xxx
     */
    esp_err_t (*set_mirror_x)(esp_lcd_touch_handle_t tp, bool mirror);

    /**
     * @brief Is mirrored X (optional)
     *
     * @param tp: Touch handler
     * @param mirror: Get X mirror value
     *
     * @return
     *      - ESP_OK on success, otherwise returns ESP_ERR_xxx
     */
    esp_err_t (*get_mirror_x)(esp_lcd_touch_handle_t tp, bool *mirror);

    /**
     * @brief Mirror Y after read coordinates
     *        If set, then not used SW mirroring.
     *
     * @param tp: Touch handler
     * @param mirror: Set Y mirror value
     *
     * @return
     *      - ESP_OK on success, otherwise returns ESP_ERR_xxx
     */
    esp_err_t (*set_mirror_y)(esp_lcd_touch_handle_t tp, bool mirror);

    /**
     * @brief Is mirrored Y (optional)
     *
     * @param tp: Touch handler
     * @param mirror: Get Y mirror value
     *
     * @return
     *      - ESP_OK on success, otherwise returns ESP_ERR_xxx
     */
    esp_err_t (*get_mirror_y)(esp_lcd_touch_handle_t tp, bool *mirror);

    /**
     * @brief Delete Touch
     *
     * @param tp: Touch handler
     *
     * @return
     *      - ESP_OK on success, otherwise returns ESP_ERR_xxx
     */
    esp_err_t (*del)(esp_lcd_touch_handle_t tp);

    /**
     * @brief Configuration structure
     */
    esp_lcd_touch_config_t config;

    /**
     * @brief Communication interface
     */
    esp_lcd_panel_io_handle_t io;

    /**
     * @brief Data structure
     */
    esp_lcd_touch_data_t data;
};


/**
 * @brief I2C address of the GT911 controller
 *
 * @note When power-on detects low level of the interrupt gpio, address is 0x5D.
 * @note Interrupt gpio is high level, address is 0x14.
 *
 */
#define ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS          (0x5D)
#define ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP   (0x14)

/**
 * @brief Touch IO configuration structure
 *
 */
#define ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG()           \
    {                                       \
        .dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, \
        .control_phase_bytes = 1,           \
        .dc_bit_offset = 0,                 \
        .lcd_cmd_bits = 16,                 \
        .flags =                            \
        {                                   \
            .disable_control_phase = 1,     \
        }                                   \
    }







#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_rgb.h"
#include "esp_timer.h"

static const char* TAG = "DISPLAY";

static esp_lcd_touch_handle_t touch_handle = NULL;
static esp_lcd_panel_handle_t lcd_handle = NULL;

/*
 * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "driver/gpio.h"
#include "esp_lcd_panel_io.h"


/* GT911 registers */
#define ESP_LCD_TOUCH_GT911_READ_KEY_REG    (0x8093)
#define ESP_LCD_TOUCH_GT911_READ_XY_REG     (0x814E)
#define ESP_LCD_TOUCH_GT911_CONFIG_REG      (0x8047)
#define ESP_LCD_TOUCH_GT911_MAX_X           (0x8048)
#define ESP_LCD_TOUCH_GT911_MAX_Y           (0x804A)
#define ESP_LCD_TOUCH_GT911_PRODUCT_ID_REG  (0x8140)
#define ESP_LCD_TOUCH_GT911_ENTER_SLEEP     (0x8040)
#define ESP_LCD_TOUCH_GT911_CHKSUM          (0x80FF)
#define ESP_LCD_TOUCH_GT911_FRESH           (0x8100)

/* GT911 support key num */
#define ESP_GT911_TOUCH_MAX_BUTTONS         (4)

uint8_t calcChecksum(uint8_t* buf, uint8_t len) {
  uint8_t ccsum = 0;
  for (uint8_t i = 0; i < len; i++) {
    ccsum += buf[i];
  }
  ccsum = (~ccsum) + 1;
  return ccsum;
}

/*******************************************************************************
* Function definitions
*******************************************************************************/
static esp_err_t esp_lcd_touch_gt911_read_data(esp_lcd_touch_handle_t tp);
static bool esp_lcd_touch_gt911_get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num);
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
static esp_err_t esp_lcd_touch_gt911_get_button_state(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state);
#endif
static esp_err_t esp_lcd_touch_gt911_del(esp_lcd_touch_handle_t tp);

/* I2C read/write */
static esp_err_t touch_gt911_i2c_read(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len);
static esp_err_t touch_gt911_i2c_write(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t data);

/* GT911 reset */
static esp_err_t touch_gt911_reset(esp_lcd_touch_handle_t tp);
/* Read status and config register */
static esp_err_t touch_gt911_read_cfg(esp_lcd_touch_handle_t tp);

/* Write config register */
//static esp_err_t touch_gt911_write_resolution(esp_lcd_touch_handle_t tp, uint16_t x_max, uint16_t y_max);

/* GT911 enter/exit sleep mode */
static esp_err_t esp_lcd_touch_gt911_enter_sleep(esp_lcd_touch_handle_t tp);
static esp_err_t esp_lcd_touch_gt911_exit_sleep(esp_lcd_touch_handle_t tp);

/***************************************************************************
* Public API functions
****************************************************************************/

esp_err_t esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_handle_t tp,esp_lcd_touch_interrupt_callback_t callback);

esp_err_t esp_lcd_touch_new_i2c_gt911(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *out_touch)
{
    esp_err_t ret = ESP_OK;

    assert(io != NULL);
    assert(config != NULL);
    assert(out_touch != NULL);

    /* Prepare main structure */
    esp_lcd_touch_handle_t esp_lcd_touch_gt911 = heap_caps_calloc(1, sizeof(esp_lcd_touch_t), MALLOC_CAP_DEFAULT);
    ESP_GOTO_ON_FALSE(esp_lcd_touch_gt911, ESP_ERR_NO_MEM, err, TAG, "no mem for GT911 controller");

    /* Communication interface */
    esp_lcd_touch_gt911->io = io;

    /* Only supported callbacks are set */
    esp_lcd_touch_gt911->read_data = esp_lcd_touch_gt911_read_data;
    esp_lcd_touch_gt911->get_xy = esp_lcd_touch_gt911_get_xy;
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
    esp_lcd_touch_gt911->get_button_state = esp_lcd_touch_gt911_get_button_state;
#endif
    esp_lcd_touch_gt911->del = esp_lcd_touch_gt911_del;
    esp_lcd_touch_gt911->enter_sleep = esp_lcd_touch_gt911_enter_sleep;
    esp_lcd_touch_gt911->exit_sleep = esp_lcd_touch_gt911_exit_sleep;

    /* Mutex */
    esp_lcd_touch_gt911->data.lock.owner = portMUX_FREE_VAL;

    /* Save config */
    memcpy(&esp_lcd_touch_gt911->config, config, sizeof(esp_lcd_touch_config_t));

    /* Prepare pin for touch interrupt */
    if (esp_lcd_touch_gt911->config.int_gpio_num != GPIO_NUM_NC) {
        const gpio_config_t int_gpio_config = {
            .mode = GPIO_MODE_INPUT,
            .intr_type = (esp_lcd_touch_gt911->config.levels.interrupt ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE),
            .pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.int_gpio_num)
        };
        ret = gpio_config(&int_gpio_config);
        ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed");

        /* Register interrupt callback */
        if (esp_lcd_touch_gt911->config.interrupt_callback) {
            esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_gt911, esp_lcd_touch_gt911->config.interrupt_callback);
        }
    }

    /* Prepare pin for touch controller reset */
    if (esp_lcd_touch_gt911->config.rst_gpio_num != GPIO_NUM_NC) {
        const gpio_config_t rst_gpio_config = {
            .mode = GPIO_MODE_OUTPUT,
            .pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.rst_gpio_num)
        };
        ret = gpio_config(&rst_gpio_config);
        ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed");
    }

    /* Reset controller */
    ret = touch_gt911_reset(esp_lcd_touch_gt911);
    ESP_GOTO_ON_ERROR(ret, err, TAG, "GT911 reset failed");

    /* Read status and config info */
    ret = touch_gt911_read_cfg(esp_lcd_touch_gt911);
    ESP_GOTO_ON_ERROR(ret, err, TAG, "GT911 init failed");

    /* Uncomment if touch max coordinates do not fit display coordinates */
    // vTaskDelay(pdMS_TO_TICKS(50));

    // ret = touch_gt911_write_resolution(esp_lcd_touch_gt911, config->x_max, config->y_max);
    // ESP_GOTO_ON_ERROR(ret, err, TAG, "GT911 write resolution failed");

err:
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Error (0x%x)! Touch controller GT911 initialization failed!", ret);
        if (esp_lcd_touch_gt911) {
	  esp_lcd_touch_gt911_del(esp_lcd_touch_gt911);
        } myDprintf("failed\n"); return ret; // added
    }

    *out_touch = esp_lcd_touch_gt911;

    myprintf("%s ok\n",__func__);
    return ret;
}

static esp_err_t esp_lcd_touch_gt911_enter_sleep(esp_lcd_touch_handle_t tp)
{
    esp_err_t err = touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_ENTER_SLEEP, 0x05);
    ESP_RETURN_ON_ERROR(err, TAG, "Enter Sleep failed!");

    return ESP_OK;
}

static esp_err_t esp_lcd_touch_gt911_exit_sleep(esp_lcd_touch_handle_t tp)
{
    esp_err_t ret;
    esp_lcd_touch_handle_t esp_lcd_touch_gt911 = tp;

    if (esp_lcd_touch_gt911->config.int_gpio_num != GPIO_NUM_NC) {
        const gpio_config_t int_gpio_config_high = {
            .mode = GPIO_MODE_OUTPUT,
            .pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.int_gpio_num)
        };
        ret = gpio_config(&int_gpio_config_high);
        ESP_RETURN_ON_ERROR(ret, TAG, "High GPIO config failed");
        gpio_set_level(esp_lcd_touch_gt911->config.int_gpio_num, 1);

        vTaskDelay(pdMS_TO_TICKS(5));

        const gpio_config_t int_gpio_config_float = {
            .mode = GPIO_MODE_OUTPUT_OD,
            .pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.int_gpio_num)
        };
        ret = gpio_config(&int_gpio_config_float);
        ESP_RETURN_ON_ERROR(ret, TAG, "Float GPIO config failed");
    }

    return ESP_OK;
}

static esp_err_t esp_lcd_touch_gt911_read_data(esp_lcd_touch_handle_t tp)
{
    esp_err_t err;
    uint8_t buf[41];
    uint8_t touch_cnt = 0;
    uint8_t clear = 0;
    size_t i = 0;

    assert(tp != NULL);

    err = touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, buf, 1);
    ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!");

    /* Any touch data? */
    if ((buf[0] & 0x80) == 0x00) {
        touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear);
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
    } else if ((buf[0] & 0x10) == 0x10) {
        /* Read all keys */
        uint8_t key_max = ((ESP_GT911_TOUCH_MAX_BUTTONS < CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS) ? \
                           (ESP_GT911_TOUCH_MAX_BUTTONS) : (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS));
        err = touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_READ_KEY_REG, &buf[0], key_max);
        ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!");

        /* Clear all */
        touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear);
        ESP_RETURN_ON_ERROR(err, TAG, "I2C write error!");

        portENTER_CRITICAL(&tp->data.lock);

        /* Buttons count */
        tp->data.buttons = key_max;
        for (i = 0; i < key_max; i++) {
            tp->data.button[i].status = buf[0] ? 1 : 0;
        }

        portEXIT_CRITICAL(&tp->data.lock);
#endif
    } else if ((buf[0] & 0x80) == 0x80) {
#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
        portENTER_CRITICAL(&tp->data.lock);
        for (i = 0; i < CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS; i++) {
            tp->data.button[i].status = 0;
        }
        portEXIT_CRITICAL(&tp->data.lock);
#endif
        /* Count of touched points */
        touch_cnt = buf[0] & 0x0f;
        if (touch_cnt > 5 || touch_cnt == 0) {
            touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear);
            return ESP_OK;
        }

        /* Read all points */
        err = touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG + 1, &buf[1], touch_cnt * 8);
        ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!");

        /* Clear all */
        err = touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear);
        ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!");

        portENTER_CRITICAL(&tp->data.lock);

        /* Number of touched points */
        touch_cnt = (touch_cnt > CONFIG_ESP_LCD_TOUCH_MAX_POINTS ? CONFIG_ESP_LCD_TOUCH_MAX_POINTS : touch_cnt);
        tp->data.points = touch_cnt;

        /* Fill all coordinates */
        for (i = 0; i < touch_cnt; i++) {
            tp->data.coords[i].x = ((uint16_t)buf[(i * 8) + 3] << 8) + buf[(i * 8) + 2];
            tp->data.coords[i].y = (((uint16_t)buf[(i * 8) + 5] << 8) + buf[(i * 8) + 4]);
            tp->data.coords[i].strength = (((uint16_t)buf[(i * 8) + 7] << 8) + buf[(i * 8) + 6]);
        }

        

        portEXIT_CRITICAL(&tp->data.lock);
    }

    return ESP_OK;
}

static bool esp_lcd_touch_gt911_get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num)
{
    assert(tp != NULL);
    assert(x != NULL);
    assert(y != NULL);
    assert(point_num != NULL);
    assert(max_point_num > 0);

    portENTER_CRITICAL(&tp->data.lock);

    /* Count of points */
    *point_num = (tp->data.points > max_point_num ? max_point_num : tp->data.points);

    for (size_t i = 0; i < *point_num; i++) {
        x[i] = tp->data.coords[i].x;
        y[i] = tp->data.coords[i].y;

        if (strength) {
            strength[i] = tp->data.coords[i].strength;
        }
    }

    /* Invalidate */
    tp->data.points = 0;

    portEXIT_CRITICAL(&tp->data.lock);

    return (*point_num > 0);
}

#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
static esp_err_t esp_lcd_touch_gt911_get_button_state(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state)
{
    esp_err_t err = ESP_OK;
    assert(tp != NULL);
    assert(state != NULL);

    *state = 0;

    portENTER_CRITICAL(&tp->data.lock);

    if (n >= tp->data.buttons) { // was n > tp->data.buttons
        err = ESP_ERR_INVALID_ARG;
    } else {
        *state = tp->data.button[n].status;
    }

    portEXIT_CRITICAL(&tp->data.lock);

    return err;
}
#endif

static esp_err_t esp_lcd_touch_gt911_del(esp_lcd_touch_handle_t tp)
{
    assert(tp != NULL);

    /* Reset GPIO pin settings */
    if (tp->config.int_gpio_num != GPIO_NUM_NC) {
        gpio_reset_pin(tp->config.int_gpio_num);
        if (tp->config.interrupt_callback) {
            gpio_isr_handler_remove(tp->config.int_gpio_num);
        }
    }

    /* Reset GPIO pin settings */
    if (tp->config.rst_gpio_num != GPIO_NUM_NC) {
        gpio_reset_pin(tp->config.rst_gpio_num);
    }

    free(tp);

    return ESP_OK;
}

/*******************************************************************************
* Private API function
*******************************************************************************/

/* Reset controller */
static esp_err_t touch_gt911_reset(esp_lcd_touch_handle_t tp)
{
    assert(tp != NULL);

    if (tp->config.rst_gpio_num != GPIO_NUM_NC) {
        ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, tp->config.levels.reset), TAG, "GPIO set level error!");
        vTaskDelay(pdMS_TO_TICKS(10));
        ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, !tp->config.levels.reset), TAG, "GPIO set level error!");
        vTaskDelay(pdMS_TO_TICKS(10));
    }

    return ESP_OK;
}

static esp_err_t touch_gt911_read_cfg(esp_lcd_touch_handle_t tp)
{

    ESP_LOGI(TAG, "Start touch_gt911_read_cfg");

    uint8_t buf[4];

    uint8_t x_res[2];
    uint8_t y_res[2];

    assert(tp != NULL);

    ESP_RETURN_ON_ERROR(touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_PRODUCT_ID_REG, (uint8_t *)&buf[0], 3), TAG, "GT911 read error!");
    ESP_RETURN_ON_ERROR(touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_CONFIG_REG, (uint8_t *)&buf[3], 1), TAG, "GT911 read error!");
 
    ESP_LOGI(TAG, "TouchPad_ID:0x%02x,0x%02x,0x%02x", buf[0], buf[1], buf[2]);
    ESP_LOGI(TAG, "TouchPad_Config_Version:%d", buf[3]);

    ESP_RETURN_ON_ERROR(touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_MAX_X, (uint8_t *)&x_res[0], 2), TAG, "GT911 read error!");
    ESP_RETURN_ON_ERROR(touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_MAX_Y, (uint8_t *)&y_res[0], 2), TAG, "GT911 read error!");

    uint16_t x_value = (x_res[1] << 8) | x_res[0];
    uint16_t y_value = (y_res[1] << 8) | y_res[0];
    ESP_LOGI(TAG, "TouchPad X 1:%u", x_res[1]);
    ESP_LOGI(TAG, "TouchPad X 0:%u", x_res[0]);

    ESP_LOGI(TAG, "TouchPad X resolution:%u", x_value);
    ESP_LOGI(TAG, "TouchPad Y resolution:%u", y_value);

    return ESP_OK;
}

#ifdef FOOP
static esp_err_t touch_gt911_write_resolution(esp_lcd_touch_handle_t tp, uint16_t x_max, uint16_t y_max)
{
    esp_err_t err;

    uint8_t len = 0x8100 - ESP_LCD_TOUCH_GT911_CONFIG_REG + 1;
    uint8_t config[len];

    ESP_LOGI(TAG, "Write resolution");

    ESP_RETURN_ON_ERROR(touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_CONFIG_REG, (uint8_t *)&config[0], len), TAG, "GT911 read error!");

    config[1] = (x_max & 0xff);
    config[2] = (x_max >> 8);
    config[3] = (y_max & 0xff);
    config[4] = (y_max >> 8);

    uint8_t checksum = calcChecksum(config, len - 2);
    ESP_LOGI(TAG, "Checksum:%u", checksum);

    config[len - 2] = calcChecksum(config, len - 2);
    config[len - 1] = 1;

    err = esp_lcd_panel_io_tx_param(tp->io, ESP_LCD_TOUCH_GT911_MAX_X, (uint8_t *)&config[1], 2);
    ESP_RETURN_ON_ERROR(err, TAG, "I2C write error!");
    err = esp_lcd_panel_io_tx_param(tp->io, ESP_LCD_TOUCH_GT911_MAX_Y, (uint8_t *)&config[3], 2);
    ESP_RETURN_ON_ERROR(err, TAG, "I2C write error!");

    err = esp_lcd_panel_io_tx_param(tp->io, ESP_LCD_TOUCH_GT911_CHKSUM, (uint8_t *)&config[len - 2], 1);
    ESP_RETURN_ON_ERROR(err, TAG, "I2C write error!");
    err = esp_lcd_panel_io_tx_param(tp->io, ESP_LCD_TOUCH_GT911_FRESH, (uint8_t *)&config[len - 1], 1);
    ESP_RETURN_ON_ERROR(err, TAG, "I2C write error!");

    return ESP_OK;
}
#endif // FOOP

static esp_err_t touch_gt911_i2c_read(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len)
{
    assert(tp != NULL);
    assert(data != NULL);

    /* Read data */
    return esp_lcd_panel_io_rx_param(tp->io, reg, data, len);
}

static esp_err_t touch_gt911_i2c_write(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t data)
{
    assert(tp != NULL);

    /* Write data */
    return esp_lcd_panel_io_tx_param(tp->io, reg, (uint8_t[]){data}, 1);
}

/*
 * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_log.h"

esp_err_t esp_lcd_touch_enter_sleep(esp_lcd_touch_handle_t tp)
{
    assert(tp != NULL);
    if (tp->enter_sleep == NULL) {
        ESP_LOGE(TAG, "Sleep mode not supported!");
        return ESP_FAIL;
    } else {
        return tp->enter_sleep(tp);
    }
}

esp_err_t esp_lcd_touch_exit_sleep(esp_lcd_touch_handle_t tp)
{
    assert(tp != NULL);
    if (tp->exit_sleep == NULL) {
        ESP_LOGE(TAG, "Sleep mode not supported!");
        return ESP_FAIL;
    } else {
        return tp->exit_sleep(tp);
    }
}

esp_err_t esp_lcd_touch_read_data(esp_lcd_touch_handle_t tp)
{
    assert(tp != NULL);
    assert(tp->read_data != NULL);

    return tp->read_data(tp);
}

bool esp_lcd_touch_get_coordinates(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num)
{
    bool touched = false;

    assert(tp != NULL);
    assert(x != NULL);
    assert(y != NULL);
    assert(tp->get_xy != NULL);

    touched = tp->get_xy(tp, x, y, strength, point_num, max_point_num);
    if (!touched) {
        return false;
    }

    /* Process coordinates by user */
    if (tp->config.process_coordinates != NULL) {
        tp->config.process_coordinates(tp, x, y, strength, point_num, max_point_num);
    }

    /* Software coordinates adjustment needed */
    bool sw_adj_needed = ((tp->config.flags.mirror_x && (tp->set_mirror_x == NULL)) ||
                          (tp->config.flags.mirror_y && (tp->set_mirror_y == NULL)) ||
                          (tp->config.flags.swap_xy && (tp->set_swap_xy == NULL)));

    /* Adjust all coordinates */
    for (int i = 0; (sw_adj_needed && i < *point_num); i++) {

        /*  Mirror X coordinates (if not supported by HW) */
        if (tp->config.flags.mirror_x && tp->set_mirror_x == NULL) {
            x[i] = tp->config.x_max - x[i];
        }

        /*  Mirror Y coordinates (if not supported by HW) */
        if (tp->config.flags.mirror_y && tp->set_mirror_y == NULL) {
            y[i] = tp->config.y_max - y[i];
        }

        /* Swap X and Y coordinates (if not supported by HW) */
        if (tp->config.flags.swap_xy && tp->set_swap_xy == NULL) {
            uint16_t tmp = x[i];
            x[i] = y[i];
            y[i] = tmp;
        }
    }

    return touched;
}

#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0)
esp_err_t esp_lcd_touch_get_button_state(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state)
{
    assert(tp != NULL);
    assert(state != NULL);

    *state = 0;

    if (tp->get_button_state) {
        return tp->get_button_state(tp, n, state);
    } else {
        return ESP_ERR_NOT_SUPPORTED;
    }

    //return ESP_OK;
}
#endif

esp_err_t esp_lcd_touch_set_swap_xy(esp_lcd_touch_handle_t tp, bool swap)
{
    assert(tp != NULL);

    tp->config.flags.swap_xy = swap;

    /* Is swap supported by HW? */
    if (tp->set_swap_xy) {
        return tp->set_swap_xy(tp, swap);
    }

    return ESP_OK;
}

esp_err_t esp_lcd_touch_get_swap_xy(esp_lcd_touch_handle_t tp, bool *swap)
{
    assert(tp != NULL);
    assert(swap != NULL);

    /* Is swap supported by HW? */
    if (tp->get_swap_xy) {
        return tp->get_swap_xy(tp, swap);
    } else {
        *swap = tp->config.flags.swap_xy;
    }

    return ESP_OK;
}

esp_err_t esp_lcd_touch_set_mirror_x(esp_lcd_touch_handle_t tp, bool mirror)
{
    assert(tp != NULL);

    tp->config.flags.mirror_x = mirror;

    /* Is mirror supported by HW? */
    if (tp->set_mirror_x) {
        return tp->set_mirror_x(tp, mirror);
    }

    return ESP_OK;
}

esp_err_t esp_lcd_touch_get_mirror_x(esp_lcd_touch_handle_t tp, bool *mirror)
{
    assert(tp != NULL);
    assert(mirror != NULL);

    /* Is swap supported by HW? */
    if (tp->get_mirror_x) {
        return tp->get_mirror_x(tp, mirror);
    } else {
        *mirror = tp->config.flags.mirror_x;
    }

    return ESP_OK;
}

esp_err_t esp_lcd_touch_set_mirror_y(esp_lcd_touch_handle_t tp, bool mirror)
{
    assert(tp != NULL);

    tp->config.flags.mirror_y = mirror;

    /* Is mirror supported by HW? */
    if (tp->set_mirror_y) {
        return tp->set_mirror_y(tp, mirror);
    }

    return ESP_OK;
}

esp_err_t esp_lcd_touch_get_mirror_y(esp_lcd_touch_handle_t tp, bool *mirror)
{
    assert(tp != NULL);
    assert(mirror != NULL);

    /* Is swap supported by HW? */
    if (tp->get_mirror_y) {
        return tp->get_mirror_y(tp, mirror);
    } else {
        *mirror = tp->config.flags.mirror_y;
    }

    return ESP_OK;
}

esp_err_t esp_lcd_touch_del(esp_lcd_touch_handle_t tp)
{
    assert(tp != NULL);

    if (tp->del != NULL) {
        return tp->del(tp);
    }

    return ESP_OK;
}

esp_err_t esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_handle_t tp, esp_lcd_touch_interrupt_callback_t callback)
{
    esp_err_t ret = ESP_OK;
    assert(tp != NULL);

    /* Interrupt pin is not selected */
    if (tp->config.int_gpio_num == GPIO_NUM_NC) {
        return ESP_ERR_INVALID_ARG;
    }

    tp->config.interrupt_callback = callback;

    if (callback != NULL) {
        ret = gpio_install_isr_service(0);
        /* ISR service can be installed from user before, then it returns invalid state */
        if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
            ESP_LOGE(TAG, "GPIO ISR install failed");
            return ret;
        }
        /* Add GPIO ISR handler */
        ret = gpio_intr_enable(tp->config.int_gpio_num);
        ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR install failed");
        ret = gpio_isr_handler_add(tp->config.int_gpio_num, (gpio_isr_t)tp->config.interrupt_callback, tp);
        ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR install failed");
    } else {
        /* Remove GPIO ISR handler */
        ret = gpio_isr_handler_remove(tp->config.int_gpio_num);
        ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR remove handler failed");
        ret = gpio_intr_disable(tp->config.int_gpio_num);
        ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR disable failed");
    }

    return ESP_OK;
}

esp_err_t esp_lcd_touch_register_interrupt_callback_with_data(esp_lcd_touch_handle_t tp, esp_lcd_touch_interrupt_callback_t callback, void *user_data)
{
    assert(tp != NULL);

    tp->config.user_data = user_data;
    return esp_lcd_touch_register_interrupt_callback(tp, callback);
}


void init_display(void);
void init_touch(i2c_master_bus_handle_t i2c_bus_handle, esp_lcd_touch_handle_t *touch_handlee);
void init_lcd(esp_lcd_panel_handle_t *panel_handle);
void init_lvgl(esp_lcd_panel_handle_t panel_handle, esp_lcd_touch_handle_t touch_handle);
bool lvgl_port_lock(int timeout_ms);
void lvgl_port_unlock(void);
void set_backlight_brightness(uint8_t brightness);
/*end copyright*/


//#define FOOP_TOUCH

/*copyright*/
/*
 * 
 */
// Problem with s/w based screen driver.
// See https://forum.squareline.io/t/elecrow-esp32s3-lvgl-screen-shifting-error-and-flash-data-storage-while-uart0-in-use/2626
// At 15/16mc screen shifts right and sets randomly. So 9mc.
// To fix do optimation menuconfig at start of this file.
// even with this 16mc is too fast.  Try 13mc
#define LCD_PIXEL_CLOCK_HZ 13000000
//#define LCD_PIXEL_CLOCK_HZ     (16 * 1000 * 1000)

#define PIN_NUM_HSYNC          GPIO_NUM_39
#define PIN_NUM_VSYNC          GPIO_NUM_40
#define PIN_NUM_DE             GPIO_NUM_41
#define PIN_NUM_PCLK           GPIO_NUM_0
// RGB565
#define PIN_NUM_DATA0          14 // 15 // 4 // GPIO_NUM_21   // B0
#define PIN_NUM_DATA1          21 // 7 // 5 // GPIO_NUM_47   // B1
#define PIN_NUM_DATA2          47 // 6 // 6 // GPIO_NUM_48   // B2
#define PIN_NUM_DATA3          48 // 5 // 7 // GPIO_NUM_45   // B3
#define PIN_NUM_DATA4          45 // 4 // 15 // GPIO_NUM_38   // B4

#define PIN_NUM_DATA5          9 //1 // GPIO_NUM_9    // G0
#define PIN_NUM_DATA6          46 // 16 // GPIO_NUM_10   // G1
#define PIN_NUM_DATA7          3 // 8 // GPIO_NUM_11   // G2
#define PIN_NUM_DATA8          8 // 3 // GPIO_NUM_12   // G3
#define PIN_NUM_DATA9          16 // 46 // GPIO_NUM_13   // G4
#define PIN_NUM_DATA10         1 // 9 // GPIO_NUM_14   // G5

#define PIN_NUM_DATA11         15 // 14 // 45 // GPIO_NUM_7    // R0
#define PIN_NUM_DATA12         7 // 21 // 48 // GPIO_NUM_17   // R1
#define PIN_NUM_DATA13         6 // 47 // 47 // GPIO_NUM_18   // R2
#define PIN_NUM_DATA14         5 // 48 // 21 // GPIO_NUM_3    // R3
#define PIN_NUM_DATA15         4 // 45 // 14 // GPIO_NUM_46   // R4
#define PIN_NUM_DISP_EN        GPIO_NUM_NC

#define HSYNC_BACK_PORCH  40 // 8
#define HSYNC_FRONT_PORCH 40 // 8
#define HSYNC_PULSE_WIDTH 48 // 4
#define VSYNC_BACK_PORCH  13 // 8
#define VSYNC_FRONT_PORCH 1 // 8
#define VSYNC_PULSE_WIDTH 31 // 4

#define LCD_H_RES         800
#define LCD_V_RES         480

#define LVGL_PORT_TICK_PERIOD_MS 2
#define LVGL_PORT_TASK_MAX_DELAY_MS 500
#define LVGL_PORT_TASK_MIN_DELAY_MS 10
#define LVGL_TASK_STACK_SIZE (4 * 1024)
#define LVGL_TASK_PRIORITY   0


static void touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data);
static void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map);
static bool on_vsync_event(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *event_data, void *user_data);
static void lvgl_port_task(void *arg);
static esp_err_t tick_init(void);

void init_i2c_expander(i2c_master_bus_handle_t i2c_bus_handle, pca9557_handle_t *expander_handle);

esp_err_t pca9557_init(i2c_master_bus_handle_t bus, pca9557_handle_t *expander_handle, const i2c_device_config_t *config)
{
    pca9557_dev_t *dev = malloc(sizeof(pca9557_dev_t));
    if (dev == NULL) {
        ESP_LOGE(TAG, "Error allocating PCA9557 device handle");
        return ESP_FAIL;
    }
    esp_err_t err = i2c_master_bus_add_device(bus, config, &dev->i2c_dev);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Error adding PCA9557 to I2C bus: %s", esp_err_to_name(err));
        free(dev);
        return err;
    }
    ESP_LOGI(TAG, "PCA9557 device initialized with address 0x%02x", config->device_address);
    *expander_handle = (pca9557_handle_t)dev;
    return err;
}
esp_err_t pca9557_set_direction(pca9557_handle_t dev, uint8_t pin, uint8_t direction)
{
    if (pin > 7) {
        ESP_LOGE(TAG, "Invalid pin number: %d", pin);
        return ESP_ERR_INVALID_ARG;
    }
    if (direction != PCA9557_INPUT && direction != PCA9557_OUTPUT) {
        ESP_LOGE(TAG, "Invalid direction: %d", direction);
        return ESP_ERR_INVALID_ARG;
    }
    uint8_t config_reg_value;
    uint8_t reg_address = PCA9557_CONFIG_REG;
    esp_err_t err = i2c_master_transmit_receive(dev->i2c_dev, &reg_address, 1, &config_reg_value, 1, -1);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Error reading configuration register: %s", esp_err_to_name(err));
        return err;
    }
    if (direction == PCA9557_INPUT) {
        config_reg_value |= (1 << pin);
    } else {
        config_reg_value &= ~(1 << pin);
    }
    uint8_t data[2] = {PCA9557_CONFIG_REG, config_reg_value};
    err = i2c_master_transmit(dev->i2c_dev, data, 2, -1);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Error writing configuration register: %s", esp_err_to_name(err));
        return err;
    }
    myprintf("Pin %d set as %s\n", pin, (direction == PCA9557_INPUT) ? "Input" : "Output");
    return ESP_OK;
}

#ifdef FOOP
esp_err_t pca9557_write_pin(pca9557_handle_t dev, uint8_t pin, int value)
{
  if(pin > 7) {
    ESP_LOGE(TAG, "Invalid pin number: %d", pin);
    return ESP_ERR_INVALID_ARG;
  }
  if (value != 0 && value != 1) {
    ESP_LOGE(TAG, "Invalid value: %d (expected 0 or 1)", value);
    return ESP_ERR_INVALID_ARG;
  }
  uint8_t output_port_value;
  uint8_t reg_address = PCA9557_OUTPUT_PORT_REG;
  esp_err_t err = i2c_master_transmit_receive(dev->i2c_dev, &reg_address, 1, &output_port_value, 1, -1);
  if (err != ESP_OK) {
    ESP_LOGE(TAG, "Error reading output port register: %s", esp_err_to_name(err));
    return err;
  }
  if (value) {
    output_port_value |= (1 << pin);
  } else {
    output_port_value &= ~(1 << pin);
  }
  uint8_t data[2] = {PCA9557_OUTPUT_PORT_REG, output_port_value};
  err = i2c_master_transmit(dev->i2c_dev, data, 2, -1);
  if (err != ESP_OK) {
    ESP_LOGE(TAG, "Error writing output port register: %s", esp_err_to_name(err));
    return err;
  }
  myprintf("Pin %d set to %d\n", pin, value);
  return ESP_OK;
}
#endif // FOOP

#ifdef FOOP
void init_backlight(pca9557_handle_t expander_handle) {
    ESP_LOGI(TAG, "Switch on the backlight -- ");

    //pca9557_set_direction(expander_handle, 3, PCA9557_OUTPUT); // was pin-1 
    //pca9557_write_pin(expander_handle, 3, 1); // 1,1
    // pin io3 high/open   pin0-7
    // pin io2 open/high
    // pin io0 open/high
    
    // Turn backlight on
    gpio_set_direction(2,GPIO_MODE_OUTPUT);
    //gpio_set_pull_mode(2,GPIO_FLOATING);
    gpio_set_level(2,1);    
}
#endif // FOOP

/**
 * @brief Initialize Display
 *
 * This function initializes the LCD display, including the backlight, touch panel, LCD panel, and LVGL library.
 */

//static i2c_master_bus_handle_t i2c_handle = NULL;

void init_display(void)
{
  //i2c_master_bus_handle_t i2c_handle = NULL;

  //  init_i2c(&i2c_handle);

  //    init_i2c_expander(i2c_handle, &expander_handle);


#ifdef FOOP_TOUCH    
    init_touch(i2c_handle, &touch_handle);
#endif    
    init_lcd(&lcd_handle);
    init_lvgl(lcd_handle, touch_handle);

    // Turn backlight on
    gpio_set_direction(2,GPIO_MODE_OUTPUT);
    gpio_set_level(2,1);
    
}

void init_i2c_expander(i2c_master_bus_handle_t i2c_bus_handle, pca9557_handle_t *expander_handle)
{
  i2c_device_config_t dev_config = {
    .dev_addr_length = I2C_ADDR_BIT_LEN_7,
    .device_address = PCA9557_I2C_ADDRESS,
    .scl_speed_hz = I2C_CLK_SPEED_HZ, 
    .flags.disable_ack_check = false, // true, // Disable ACK check
  };
  esp_err_t ret=pca9557_init(i2c_bus_handle, expander_handle, &dev_config); // PCA9557 Treiber initialisieren [5]
  if(ret != ESP_OK) {
    ESP_LOGE(TAG, "failed PCA9557");
    //i2c_master_bus_delete(i2c_bus); // Bei Fehler I2C Bus wieder freigeben
    return;
  }
  ESP_LOGI(TAG, "PCA9557 inited ok");
}

/**
 * @brief Initialize Touch Driver
 *
 * This function installs the touch driver, configures the I2C interface for touch communication,
 * initializes the touch controller, and creates a touch handle for touch input.
 *
 * @param[out] touch_handle Pointer to the handle for the initialized touch controller.
 */
void init_touch(i2c_master_bus_handle_t i2c_bus_handle, esp_lcd_touch_handle_t *touch_handle)
{

  ESP_LOGI(TAG, "Install Touch driver");
  /* Initialize touch */
  const esp_lcd_touch_config_t tp_cfg = {
    .x_max = LCD_H_RES,
    .y_max = LCD_V_RES,
    .rst_gpio_num = GPIO_NUM_NC,
#ifdef FOOP_TOUCH	
    .int_gpio_num = GPIO_NUM_NC,   // was 1, but that is used
#endif	
    .levels = {
      .reset = 0,
      .interrupt = 0,
    },
    .flags = {
      .swap_xy = 0,
      .mirror_x = 0,
      .mirror_y = 0,
    },
  };
  esp_lcd_panel_io_handle_t tp_io_handle = NULL;
  
  //const esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG();
  esp_lcd_panel_io_i2c_config_t tp_io_config = {           
    .dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, 
    .scl_speed_hz = I2C_CLK_SPEED_HZ,
    .control_phase_bytes = 1,           
    .dc_bit_offset = 0,                 
    .lcd_cmd_bits = 16,                 
    .flags =                            
    {                                   
      .disable_control_phase = 1,     
    }                                       
  };
  
  
  ESP_LOGI(TAG, "Create LCD panel IO handle");
  esp_lcd_new_panel_io_i2c_v2(i2c_bus_handle,&tp_io_config,&tp_io_handle);
  ESP_LOGI(TAG, "Create a new GT911 touch driver");
  esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, touch_handle);
}


/**
 * @brief Initialize RGB LCD Panel
 *
 * This function installs the RGB LCD panel driver, creates semaphores for synchronization,
 * configures the RGB LCD panel with the provided parameters, registers event callbacks,
 * resets and initializes the RGB LCD panel.
 *
 * @param[out] panel_handle Pointer to the handle for the initialized RGB LCD panel.
 */
void init_lcd(esp_lcd_panel_handle_t *panel_handle)
{    
  ESP_LOGI(TAG, "Install RGB LCD panel driver");
  
  // sem_vsync_end = xSemaphoreCreateBinary();
  // sem_gui_ready = xSemaphoreCreateBinary();
  
  esp_lcd_rgb_panel_config_t panel_config = {
    .data_width = 16, // RGB565 in parallel mode, thus 16bit in width
    .psram_trans_align = 64,
    .num_fbs = 2, // Use 2 frame buffers
    .clk_src = LCD_CLK_SRC_DEFAULT,
    .disp_gpio_num = PIN_NUM_DISP_EN,
    .pclk_gpio_num = PIN_NUM_PCLK,
    .vsync_gpio_num = PIN_NUM_VSYNC,
    .hsync_gpio_num = PIN_NUM_HSYNC,
    .de_gpio_num = PIN_NUM_DE,
    .data_gpio_nums = {
      PIN_NUM_DATA0,
      PIN_NUM_DATA1,
      PIN_NUM_DATA2,
      PIN_NUM_DATA3,
      PIN_NUM_DATA4,
      PIN_NUM_DATA5,
      PIN_NUM_DATA6,
      PIN_NUM_DATA7,
      PIN_NUM_DATA8,
      PIN_NUM_DATA9,
      PIN_NUM_DATA10,
      PIN_NUM_DATA11,
      PIN_NUM_DATA12,
      PIN_NUM_DATA13,
      PIN_NUM_DATA14,
      PIN_NUM_DATA15,
    },
    .timings = {
      .pclk_hz = LCD_PIXEL_CLOCK_HZ,
      .h_res = LCD_H_RES,
      .v_res = LCD_V_RES,
      .hsync_back_porch = HSYNC_BACK_PORCH,
      .hsync_front_porch = HSYNC_FRONT_PORCH,
      .hsync_pulse_width = HSYNC_PULSE_WIDTH,
      .vsync_back_porch = VSYNC_BACK_PORCH,
      .vsync_front_porch = VSYNC_FRONT_PORCH,
      .vsync_pulse_width = VSYNC_PULSE_WIDTH,
    },
    .flags.fb_in_psram = true, // allocate frame buffer in PSRAM
  };
  ESP_LOGI(TAG, "Create RGB LCD panel");
  esp_lcd_new_rgb_panel(&panel_config, panel_handle);
    
  ESP_LOGI(TAG, "Register event callbacks");
  esp_lcd_rgb_panel_event_callbacks_t cbs = {
    .on_vsync = on_vsync_event,
  };
  esp_lcd_rgb_panel_register_event_callbacks(*panel_handle, &cbs, NULL);
  
  ESP_LOGI(TAG, "Initialize RGB LCD panel");
  esp_lcd_panel_reset(*panel_handle);
  esp_lcd_panel_init(*panel_handle);
}



/**
 * @brief Initialize LVGL Library
 *
 * This function initializes the LVGL library, allocates separate draw buffers from PSRAM,
 * registers the display driver and input device driver to LVGL, creates a semaphore for
 * LVGL synchronization, and starts the LVGL port task.
 *
 * @param[in] panel_handle Handle to the LCD panel associated with LVGL.
 * @param[in] touch_handle Handle to the touchpad device associated with LVGL.
 */
void init_lvgl(esp_lcd_panel_handle_t panel_handle, esp_lcd_touch_handle_t touch_handle)
{
  ESP_LOGI(TAG, "Initialize LVGL library");
  
  lvgl_mux = xSemaphoreCreateMutex();
  
  static lv_disp_draw_buf_t disp_buf; // contains internal graphic buffer(s) called draw buffer(s)
  static lv_disp_drv_t disp_drv;      // contains callback functions
  
  lv_init();
  
  ESP_ERROR_CHECK(tick_init());
  
  void *buf1 = NULL;
  void *buf2 = NULL;
  
  ESP_LOGI(TAG,"Use frame buffers as LVGL draw buffers");
  ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(panel_handle,2,&buf1, &buf2));
  // initialize LVGL draw buffers
  lv_disp_draw_buf_init(&disp_buf,buf1,buf2, LCD_H_RES * LCD_V_RES);
  
  ESP_LOGI(TAG, "Register display driver to LVGL");
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = LCD_H_RES;
  disp_drv.ver_res = LCD_V_RES;
  disp_drv.flush_cb = lvgl_flush_cb;
  disp_drv.draw_buf = &disp_buf;
  disp_drv.user_data = panel_handle;
  disp_drv.full_refresh = true; // the full_refresh mode can maintain the synchronization between the two frame buffers
  lv_disp_drv_register(&disp_drv);
  
#ifdef FOOP_TOUCH    
  ESP_LOGI(TAG, "Register input device driver to LVGL");
  static lv_indev_drv_t indev_drv;
  lv_indev_drv_init(&indev_drv);
  indev_drv.type = LV_INDEV_TYPE_POINTER;
  indev_drv.read_cb = touchpad_read;
  indev_drv.user_data = touch_handle;
  lv_indev_drv_register(&indev_drv);
#endif // FOOP
  ESP_LOGI(TAG, "Start lv_timer_handler task");

  // done after periodic_ui in myuitask()
  //xTaskCreatePinnedToCore(lvgl_port_task,"lvgl", LVGL_TASK_STACK_SIZE, NULL, LVGL_TASK_PRIORITY, NULL, 1);
  
}

/**
 * @brief Touchpad Read Function
 *
 * This function reads touchpad input and updates the LVGL input device data accordingly.
 *
 * @param[in] indev_driver Pointer to the LVGL input device driver structure.
 * @param[out] data Pointer to the LVGL input device data structure to be updated.
 */
static void touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)
{
  esp_lcd_touch_handle_t touch_handle = (esp_lcd_touch_handle_t)indev_driver->user_data;

  uint16_t touchpad_x;
  uint16_t touchpad_y;
  uint16_t touch_strength;
  uint8_t touch_cnt = 0;
  
  data->state = LV_INDEV_STATE_REL;
  
  esp_lcd_touch_read_data(touch_handle);
  bool touchpad_pressed = esp_lcd_touch_get_coordinates(touch_handle, &touchpad_x, &touchpad_y, &touch_strength, &touch_cnt, 1);
  if(touchpad_pressed) {
    //ESP_LOGI(TAG, "Touchpad_read %d %d", touchpad_x, touchpad_y);
    data->state = LV_INDEV_STATE_PR;    
    /*Set the coordinates*/
    data->point.x = touchpad_x;
    data->point.y = touchpad_y;
  }
}
/**
 * @brief LVGL Flush Callback
 *
 * This callback function is called by LVGL to flush a portion of the display buffer to the physical display.
 * It passes the draw buffer to the LCD panel driver, indicating the area that needs to be updated.
 *
 * @param[in] drv Pointer to the display driver structure.
 * @param[in] area Pointer to the area that needs to be flushed.
 * @param[in] color_map Pointer to the color map containing pixel data to be flushed.
 */
static void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
  esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;

  int offsetx1 = area->x1;
  int offsetx2 = area->x2;
  int offsety1 = area->y1;
  int offsety2 = area->y2;
  
  // pass the draw buffer to the driver
  esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
  lv_disp_flush_ready(drv);
}
/**
 * @brief Handles VSYNC events for an ESP32 LCD panel.
 *
 * This function waits until LVGL has finished its operations, indicated by the
 * `sem_gui_ready` semaphore being taken. Once LVGL is ready, it signals that
 * the VSYNC event has ended by giving the `sem_vsync_end` semaphore.
 *
 * @param[in] panel Handle to the LCD panel associated with the VSYNC event.
 * @param[in] event_data Pointer to a structure containing data related to the VSYNC event.
 * @param[in] user_data User data pointer passed when registering the VSYNC event handler.
 * @return
 *     - `true` if the task was woken up due to handling the VSYNC event, indicating that it's safe
 *       to proceed with flushing the buffer.
 *     - `false` otherwise.
 */
static bool on_vsync_event(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *event_data, void *user_data)
{
  BaseType_t high_task_awoken = pdFALSE;
  
  // Wait until LVGL has finished 
  // if (xSemaphoreTakeFromISR(sem_gui_ready, &high_task_awoken) == pdTRUE) {
  //     // Indicate that the VSYNC event has ended, and it's safe to proceed with flushing the buffer.
  //     xSemaphoreGiveFromISR(sem_vsync_end, &high_task_awoken);
  // }
  
  return high_task_awoken == pdTRUE;
}
static void tick_increment(void *arg)
{
  /* Tell LVGL how many milliseconds have elapsed */
  lv_tick_inc(LVGL_PORT_TICK_PERIOD_MS); // Increment the LVGL tick count
}
static esp_err_t tick_init(void)
{
  // Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
  const esp_timer_create_args_t lvgl_tick_timer_args = {
    .callback = &tick_increment, // Set the callback function for the timer
    .name = "LVGL tick" // Name of the timer
  };
  esp_timer_handle_t lvgl_tick_timer = NULL; // Timer handle
  ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer)); // Create the timer
  return esp_timer_start_periodic(lvgl_tick_timer, LVGL_PORT_TICK_PERIOD_MS * 1000); // Start the timer
}
/**
 * @brief LVGL Port Task
 *
 * This task handles LVGL operations in the background. It periodically calls
 * the LVGL timer handler to update the GUI.
 *
 * @param[in] arg Pointer to task arguments (not used).
 */
static void lvgl_port_task(void *arg)
{
  uint32_t task_delay_ms = LVGL_PORT_TASK_MAX_DELAY_MS; // Set initial task delay
  while(1) {

    if(lvgl_port_lock(-1)) { // Try to lock the LVGL mutex
      task_delay_ms = lv_timer_handler(); // Handle LVGL timer events
      lvgl_port_unlock(); // Unlock the mutex
    }    
    // Ensure the delay time is within limits
    if (task_delay_ms > LVGL_PORT_TASK_MAX_DELAY_MS) {
      task_delay_ms = LVGL_PORT_TASK_MAX_DELAY_MS;
    } else if (task_delay_ms < LVGL_PORT_TASK_MIN_DELAY_MS) {
      task_delay_ms = LVGL_PORT_TASK_MIN_DELAY_MS;
    }
    vTaskDelay(pdMS_TO_TICKS(task_delay_ms)); // Delay the task for the calculated time    
  }
}
bool lvgl_port_lock(int timeout_ms)
{
  assert(lvgl_mux && "lvgl_port_init must be called first"); // Ensure the mutex is initialized
  
  const TickType_t timeout_ticks = (timeout_ms < 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); // Convert timeout to ticks
  return xSemaphoreTakeRecursive(lvgl_mux, timeout_ticks) == pdTRUE; // Try to take the mutex
}
void lvgl_port_unlock(void)
{
  assert(lvgl_mux && "lvgl_port_init must be called first"); // Ensure the mutex is initialized
  xSemaphoreGiveRecursive(lvgl_mux); // Release the mutex
}
/*end copyright*/
#endif // CONFIG_IDF_TARGET_ESP32S3 


/*
 * my default hash function
 */
#define USE_SHA3  // used by fwupdate

#ifdef USE_SHA3
/*copyright*/
/* -------------------------------------------------------------------------
 * Works when compiled for either 32-bit or 64-bit targets, optimized for 
 * 64 bit.
 *
 * Canonical implementation of Init/Update/Finalize for SHA-3 byte input. 
 *
 * SHA3-256, SHA3-384, SHA-512 are implemented. SHA-224 can easily be added.
 *
 * Based on code from http://keccak.noekeon.org/ .
 *
 * I place the code that I wrote into public domain, free to use. 
 *
 * I would appreciate if you give credits to this work if you used it to 
 * write or test * your code.
 *
 * Aug 2015. Andrey Jivsov. crypto@brainhub.org
 * ---------------------------------------------------------------------- */

#include <stdio.h>
#include <stdint.h>
#include <string.h>

/* 'Words' here refers to uint64_t */
#define SHA3_KECCAK_SPONGE_WORDS (((1600)/8/*bits to byte*/)/sizeof(uint64_t))

typedef struct sha3_context_ {
    uint64_t saved;             /* the portion of the input message that we
                                 * didn't consume yet */
    union {                     /* Keccak's state */
        uint64_t s[SHA3_KECCAK_SPONGE_WORDS];
        uint8_t sb[SHA3_KECCAK_SPONGE_WORDS * 8];
    };
    unsigned byteIndex;         /* 0..7--the next byte after the set one
                                 * (starts from 0; 0--none are buffered) */
    unsigned wordIndex;         /* 0..24--the next word to integrate input
                                 * (starts from 0) */
    unsigned capacityWords;     /* the double size of the hash output in
                                 * words (e.g. 16 for Keccak 512) */
} sha3_context;

int sha3_context_size(void) { return sizeof(sha3_context); }

enum SHA3_FLAGS {
    SHA3_FLAGS_NONE=0,
    SHA3_FLAGS_KECCAK=1
};

enum SHA3_RETURN {
    SHA3_RETURN_OK=0,
    SHA3_RETURN_BAD_PARAMS=1
};
typedef enum SHA3_RETURN sha3_return_t;

/* For Init or Reset call these: */
sha3_return_t sha3_Init(void *priv, unsigned bitSize);

void sha3_Init256(void *priv);
void sha3_Init384(void *priv);
void sha3_Init512(void *priv);

//enum SHA3_FLAGS sha3_SetFlags(void *priv, enum SHA3_FLAGS);

void sha3_Update(void *priv, void const *bufIn, size_t len);

void const *sha3_Finalize(void *priv);

/* Single-call hashing */
sha3_return_t sha3_HashBuffer( 
    unsigned bitSize,   /* 256, 384, 512 */
    enum SHA3_FLAGS flags, /* SHA3_FLAGS_NONE or SHA3_FLAGS_KECCAK */
    const void *in, unsigned inBytes, 
    void *out, unsigned outBytes );     /* up to bitSize/8; truncation OK */


#define SHA3_ASSERT( x )
#if defined(_MSC_VER)
#define SHA3_TRACE( format, ...)
#define SHA3_TRACE_BUF( format, buf, l, ...)
#else
#define SHA3_TRACE(format, args...)
#define SHA3_TRACE_BUF(format, buf, l, args...)
#endif

/* 
 * This flag is used to configure "pure" Keccak, as opposed to NIST SHA3.
 */
#define SHA3_USE_KECCAK_FLAG 0x80000000
#define SHA3_CW(x) ((x) & (~SHA3_USE_KECCAK_FLAG))


#if defined(_MSC_VER)
#define SHA3_CONST(x) x
#else
#define SHA3_CONST(x) x##L
#endif

#ifndef SHA3_ROTL64
#define SHA3_ROTL64(x, y) \
        (((x) << (y)) | ((x) >> ((sizeof(uint64_t)*8) - (y))))
#endif

static const uint64_t keccakf_rndc[24] = {
    SHA3_CONST(0x0000000000000001UL), SHA3_CONST(0x0000000000008082UL),
    SHA3_CONST(0x800000000000808aUL), SHA3_CONST(0x8000000080008000UL),
    SHA3_CONST(0x000000000000808bUL), SHA3_CONST(0x0000000080000001UL),
    SHA3_CONST(0x8000000080008081UL), SHA3_CONST(0x8000000000008009UL),
    SHA3_CONST(0x000000000000008aUL), SHA3_CONST(0x0000000000000088UL),
    SHA3_CONST(0x0000000080008009UL), SHA3_CONST(0x000000008000000aUL),
    SHA3_CONST(0x000000008000808bUL), SHA3_CONST(0x800000000000008bUL),
    SHA3_CONST(0x8000000000008089UL), SHA3_CONST(0x8000000000008003UL),
    SHA3_CONST(0x8000000000008002UL), SHA3_CONST(0x8000000000000080UL),
    SHA3_CONST(0x000000000000800aUL), SHA3_CONST(0x800000008000000aUL),
    SHA3_CONST(0x8000000080008081UL), SHA3_CONST(0x8000000000008080UL),
    SHA3_CONST(0x0000000080000001UL), SHA3_CONST(0x8000000080008008UL)
};

static const unsigned keccakf_rotc[24] = {
    1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62,
    18, 39, 61, 20, 44
};

static const unsigned keccakf_piln[24] = {
    10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20,
    14, 22, 9, 6, 1
};

/* generally called after SHA3_KECCAK_SPONGE_WORDS-ctx->capacityWords words 
 * are XORed into the state s 
 */
static void keccakf(uint64_t s[25])
{
  int i, j, round;
  uint64_t t, bc[5];
#define KECCAK_ROUNDS 24
  
  for(round = 0; round < KECCAK_ROUNDS; round++) {
    
    /* Theta */
    for(i = 0; i < 5; i++) bc[i] = s[i] ^ s[i + 5] ^ s[i + 10] ^ s[i + 15] ^ s[i + 20];
    
    for(i = 0; i < 5; i++) {
      t = bc[(i + 4) % 5] ^ SHA3_ROTL64(bc[(i + 1) % 5], 1);
      for(j = 0; j < 25; j += 5)
        s[j + i] ^= t;
    }

    /* Rho Pi */
    t = s[1];
    for(i = 0; i < 24; i++) {
      j = keccakf_piln[i];
      bc[0] = s[j];
      s[j] = SHA3_ROTL64(t, keccakf_rotc[i]);
      t = bc[0];
    }

    /* Chi */
    for(j = 0; j < 25; j += 5) {
      for(i = 0; i < 5; i++)
        bc[i] = s[j + i];
      for(i = 0; i < 5; i++)
        s[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5];
    }
    
    /* Iota */
    s[0] ^= keccakf_rndc[round];
  }
}

/* *************************** Public Inteface ************************ */

/* For Init or Reset call these: */
sha3_return_t sha3_Init(void *priv, unsigned bitSize) {
  sha3_context *ctx = (sha3_context *) priv;
  if( bitSize != 256 && bitSize != 384 && bitSize != 512 )
    return SHA3_RETURN_BAD_PARAMS;
  memset(ctx, 0, sizeof(*ctx));
  ctx->capacityWords = 2 * bitSize / (8 * sizeof(uint64_t));
  return SHA3_RETURN_OK;
}
void sha3_Init256(void *priv)
{
  sha3_Init(priv, 256);
}
void sha3_Init384(void *priv)
{
  sha3_Init(priv, 384);
}
void sha3_Init512(void *priv)
{
  sha3_Init(priv, 512);
}
#ifdef FOOP
enum SHA3_FLAGS sha3_SetFlags(void *priv, enum SHA3_FLAGS flags)
{
  sha3_context *ctx = (sha3_context *) priv;
  //flags &= SHA3_FLAGS_KECCAK;
  flags = flags & (enum SHA3_FLAGS)SHA3_FLAGS_KECCAK;
  ctx->capacityWords |= (flags == SHA3_FLAGS_KECCAK ? SHA3_USE_KECCAK_FLAG : 0);
  return flags;
}
#endif // FOOP
int sha3_SetFlags(void *priv, int flags)
{
  sha3_context *ctx = (sha3_context *) priv;
  flags &= SHA3_FLAGS_KECCAK;
  ctx->capacityWords |= (flags == SHA3_FLAGS_KECCAK ? SHA3_USE_KECCAK_FLAG : 0);
  return flags;
}
void sha3_Update(void *priv, void const *bufIn, size_t len)
{
  sha3_context *ctx = (sha3_context *) priv;
  
  /* 0...7 -- how much is needed to have a word */
  unsigned old_tail = (8 - ctx->byteIndex) & 7;
  
  size_t words;
  unsigned tail;
  size_t i;
  
  const uint8_t *buf = (const uint8_t *)bufIn;
  
  SHA3_TRACE_BUF("called to update with:", buf, len);
  
  SHA3_ASSERT(ctx->byteIndex < 8);
  SHA3_ASSERT(ctx->wordIndex < sizeof(ctx->s) / sizeof(ctx->s[0]));
  
  if(len < old_tail) {        /* have no complete word or haven't started 
                               * the word yet */
    SHA3_TRACE("because %d<%d, store it and return", (unsigned)len,
               (unsigned)old_tail);
    /* endian-independent code follows: */
    while (len--) ctx->saved |= (uint64_t) (*(buf++)) << ((ctx->byteIndex++) * 8);
    SHA3_ASSERT(ctx->byteIndex < 8);
    return;
  }
  
  if(old_tail) {              /* will have one word to process */
    SHA3_TRACE("completing one word with %d bytes", (unsigned)old_tail);
    /* endian-independent code follows: */
    len -= old_tail;
    while (old_tail--) ctx->saved |= (uint64_t) (*(buf++)) << ((ctx->byteIndex++) * 8);
    
    /* now ready to add saved to the sponge */
    ctx->s[ctx->wordIndex] ^= ctx->saved;
    SHA3_ASSERT(ctx->byteIndex == 8);
    ctx->byteIndex = 0;
    ctx->saved = 0;
    if(++ctx->wordIndex ==
       (SHA3_KECCAK_SPONGE_WORDS - SHA3_CW(ctx->capacityWords))) {
      keccakf(ctx->s);
      ctx->wordIndex = 0;
    }
  }
  
  /* now work in full words directly from input */
  
  SHA3_ASSERT(ctx->byteIndex == 0);
  
  words = len / sizeof(uint64_t);
  tail = len - words * sizeof(uint64_t);
  
  SHA3_TRACE("have %d full words to process", (unsigned)words);
  
  for(i = 0; i < words; i++, buf += sizeof(uint64_t)) {
    const uint64_t t = (uint64_t) (buf[0]) |
      ((uint64_t) (buf[1]) << 8 * 1) |
      ((uint64_t) (buf[2]) << 8 * 2) |
      ((uint64_t) (buf[3]) << 8 * 3) |
      ((uint64_t) (buf[4]) << 8 * 4) |
      ((uint64_t) (buf[5]) << 8 * 5) |
      ((uint64_t) (buf[6]) << 8 * 6) |
      ((uint64_t) (buf[7]) << 8 * 7);
#if defined(__x86_64__ ) || defined(__i386__)
    SHA3_ASSERT(memcmp(&t, buf, 8) == 0);
#endif
    ctx->s[ctx->wordIndex] ^= t;
    if(++ctx->wordIndex ==
       (SHA3_KECCAK_SPONGE_WORDS - SHA3_CW(ctx->capacityWords))) {
      keccakf(ctx->s);
      ctx->wordIndex = 0;
    }
  }

  SHA3_TRACE("have %d bytes left to process, save them", (unsigned)tail);
  
  /* finally, save the partial word */
  SHA3_ASSERT(ctx->byteIndex == 0 && tail < 8);
  while (tail--) {
    SHA3_TRACE("Store byte %02x '%c'", *buf, *buf);
    ctx->saved |= (uint64_t) (*(buf++)) << ((ctx->byteIndex++) * 8);
  }
  SHA3_ASSERT(ctx->byteIndex < 8);
  SHA3_TRACE("Have saved=0x%016" PRIx64 " at the end", ctx->saved);
}
/* This is simply the 'update' with the padding block.
 * The padding block is 0x01 || 0x00* || 0x80. First 0x01 and last 0x80 
 * bytes are always present, but they can be the same byte.
 */
void const *sha3_Finalize(void *priv)
{
  sha3_context *ctx = (sha3_context *) priv;
  
  SHA3_TRACE("called with %d bytes in the buffer", ctx->byteIndex);
  
  /* Append 2-bit suffix 01, per SHA-3 spec. Instead of 1 for padding we
   * use 1<<2 below. The 0x02 below corresponds to the suffix 01.
   * Overall, we feed 0, then 1, and finally 1 to start padding. Without
   * M || 01, we would simply use 1 to start padding. */
  
  uint64_t t;
  
  if( ctx->capacityWords & SHA3_USE_KECCAK_FLAG ) {
    /* Keccak version */
    t = (uint64_t)(((uint64_t) 1) << (ctx->byteIndex * 8));
  }
  else {
    /* SHA3 version */
    t = (uint64_t)(((uint64_t)(0x02 | (1 << 2))) << ((ctx->byteIndex) * 8));
  }
  
  ctx->s[ctx->wordIndex] ^= ctx->saved ^ t;
  
  ctx->s[SHA3_KECCAK_SPONGE_WORDS - SHA3_CW(ctx->capacityWords) - 1] ^=
    SHA3_CONST(0x8000000000000000UL);
  keccakf(ctx->s);
  
  /* Return first bytes of the ctx->s. This conversion is not needed for
   * little-endian platforms e.g. wrap with #if !defined(__BYTE_ORDER__)
   * || !defined(__ORDER_LITTLE_ENDIAN__) || __BYTE_ORDER__!=__ORDER_LITTLE_ENDIAN__ 
   *    ... the conversion below ...
   * #endif */
  {
    unsigned i;
    for(i = 0; i < SHA3_KECCAK_SPONGE_WORDS; i++) {
      const unsigned t1 = (uint32_t) ctx->s[i];
      const unsigned t2 = (uint32_t) ((ctx->s[i] >> 16) >> 16);
      ctx->sb[i * 8 + 0] = (uint8_t) (t1);
      ctx->sb[i * 8 + 1] = (uint8_t) (t1 >> 8);
      ctx->sb[i * 8 + 2] = (uint8_t) (t1 >> 16);
      ctx->sb[i * 8 + 3] = (uint8_t) (t1 >> 24);
      ctx->sb[i * 8 + 4] = (uint8_t) (t2);
      ctx->sb[i * 8 + 5] = (uint8_t) (t2 >> 8);
      ctx->sb[i * 8 + 6] = (uint8_t) (t2 >> 16);
      ctx->sb[i * 8 + 7] = (uint8_t) (t2 >> 24);
    }
  }
  
  SHA3_TRACE_BUF("Hash: (first 32 bytes)", ctx->sb, 256 / 8);
  
  return (ctx->sb);
}
sha3_return_t sha3_HashBuffer( unsigned bitSize, enum SHA3_FLAGS flags, const void *in, unsigned inBytes, void *out, unsigned outBytes ) {
  sha3_return_t err;
  sha3_context c;
  
  err = sha3_Init(&c, bitSize);
  if( err != SHA3_RETURN_OK )
    return err;
  if( sha3_SetFlags(&c, flags) != flags ) {
    return SHA3_RETURN_BAD_PARAMS;
  }
  sha3_Update(&c, in, inBytes);
  const void *h = sha3_Finalize(&c);
  
  if(outBytes > bitSize/8)
    outBytes = bitSize/8;
  memcpy(out, h, outBytes);
  return SHA3_RETURN_OK;
}
/*endcopyright*/

#ifdef MYHASH320
void *myhash32(void *cb,uint8_t *in,int n)
{
  static sha3_context c;
  static uint8_t out[32];
  if(cb == NULL) {
    sha3_Init256(&c);
    return (void *)&c;
  }
  if(n == 0 && in == NULL) {
    memset(out,0,sizeof(out));
    memcpy(out,sha3_Finalize(cb),32);
    return (void *)out;
  }
  if(n > 0 && in) {
    sha3_Update(cb,in,n);
    return cb;
  }
  return NULL;
}
#endif // MYHASH320
int myhash32(void *cb,uint8_t *in,int n)
{
  if(n == -1) return max(sizeof(sha3_context),32);
  if(n == 0) {
    sha3_Init256(cb);
    return 0;
  }
  if(cb == NULL) return -1;
  if(n == -2) {
    uint8_t out[32];
    memset(out,0,sizeof(out));
    memcpy(out,sha3_Finalize(cb),32);
    memcpy(cb,out,32);
    return 0;
  }
  if(in == NULL || n < 0) return -1;
  sha3_Update(cb,in,n);
  return 0;
}
#endif // USE_SHA3


/*********************************************************************
 * ED25519
 *********************************************************************/
#define USE_ED25519

#ifdef USE_ED25519
/*copyright*/
/* 
 * modified ed25519 implementation based on: https://github.com/orlp/ed25519
 *
 Copyright (c) 2015 Orson Peters <orsonpeters@gmail.com>
 This software is provided 'as-is', without any express or implied warranty. In no event will the
 authors be held liable for any damages arising from the use of this software. 
 Permission is granted to anyone to use this software for any purpose, including commercial
 applications, and to alter it and redistribute it freely, subject to the following restrictions: 
 1. The origin of this software must not be misrepresented; you must not claim that you wrote the
 original software. If you use this software in a product, an acknowledgment in the product
 documentation would be appreciated but is not required. 
 2. Altered source versions must be plainly marked as such, and must not be misrepresented as
 being the original software. 
 3. This notice may not be removed or altered from any source distribution.
 *
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

/* #define ED25519_DLL */
//#include "src/ed25519.h"
#ifndef ED25519_H
#define ED25519_H

#include <stddef.h>

#if defined(_WIN32)
    #if defined(ED25519_BUILD_DLL)
        #define ED25519_DECLSPEC __declspec(dllexport)
    #elif defined(ED25519_DLL)
        #define ED25519_DECLSPEC __declspec(dllimport)
    #else
        #define ED25519_DECLSPEC
    #endif
#else
    #define ED25519_DECLSPEC
#endif


#ifdef __cplusplus
extern "C" {
#endif

#ifndef ED25519_NO_SEED
int ED25519_DECLSPEC ed25519_create_seed(unsigned char *seed);
#endif

void ED25519_DECLSPEC ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed);
void ED25519_DECLSPEC ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key);
int ED25519_DECLSPEC ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key);
void ED25519_DECLSPEC ed25519_add_scalar(unsigned char *public_key, unsigned char *private_key, const unsigned char *scalar);
void ED25519_DECLSPEC ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key);


#ifdef __cplusplus
}
#endif

#endif


//#include "src/ge.h"
#ifndef GE_H
#define GE_H

//#include "fe.h"
#ifndef FE_H
#define FE_H

//#include "fixedint.h"
#ifndef __STDC_VERSION__   // needed below
#define __STDC_VERSION__ 0
#endif
/*
  Portable header to provide the 32 and 64 bits type.  
  Not a compatible replacement for <stdint.h>, do not blindly use it as such.
*/

#if ((defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) || (defined(__WATCOMC__) && (defined(_STDINT_H_INCLUDED) || __WATCOMC__ >= 1250)) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_) || defined(__UINT_FAST64_TYPE__)) )) && !defined(FIXEDINT_H_INCLUDED)
    #include <stdint.h>
    #define FIXEDINT_H_INCLUDED

    #if defined(__WATCOMC__) && __WATCOMC__ >= 1250 && !defined(UINT64_C)
        #include <limits.h>
        #define UINT64_C(x) (x + (UINT64_MAX - UINT64_MAX))
    #endif
#endif


#ifndef FIXEDINT_H_INCLUDED
    #define FIXEDINT_H_INCLUDED
    
    #include <limits.h>

    /* (u)int32_t */
    #ifndef uint32_t
        #if (ULONG_MAX == 0xffffffffUL)
            typedef unsigned long uint32_t;
        #elif (UINT_MAX == 0xffffffffUL)
            typedef unsigned int uint32_t;
        #elif (USHRT_MAX == 0xffffffffUL)
            typedef unsigned short uint32_t;
        #endif
    #endif


    #ifndef int32_t
        #if (LONG_MAX == 0x7fffffffL)
            typedef signed long int32_t;
        #elif (INT_MAX == 0x7fffffffL)
            typedef signed int int32_t;
        #elif (SHRT_MAX == 0x7fffffffL)
            typedef signed short int32_t;
        #endif
    #endif


    /* (u)int64_t */
    #if (defined(__STDC__) && defined(__STDC_VERSION__) && __STDC__ && __STDC_VERSION__ >= 199901L)
        typedef long long int64_t;
        typedef unsigned long long uint64_t;

        #define UINT64_C(v) v ##ULL
        #define INT64_C(v) v ##LL
    #elif defined(__GNUC__)
        __extension__ typedef long long int64_t;
        __extension__ typedef unsigned long long uint64_t;

        #define UINT64_C(v) v ##ULL
        #define INT64_C(v) v ##LL
    #elif defined(__MWERKS__) || defined(__SUNPRO_C) || defined(__SUNPRO_CC) || defined(__APPLE_CC__) || defined(_LONG_LONG) || defined(_CRAYC)
        typedef long long int64_t;
        typedef unsigned long long uint64_t;

        #define UINT64_C(v) v ##ULL
        #define INT64_C(v) v ##LL
    #elif (defined(__WATCOMC__) && defined(__WATCOM_INT64__)) || (defined(_MSC_VER) && _INTEGRAL_MAX_BITS >= 64) || (defined(__BORLANDC__) && __BORLANDC__ > 0x460) || defined(__alpha) || defined(__DECC)
        typedef __int64 int64_t;
        typedef unsigned __int64 uint64_t;

        #define UINT64_C(v) v ##UI64
        #define INT64_C(v) v ##I64
    #endif
#endif


/*
    fe means field element.
    Here the field is \Z/(2^255-19).
    An element t, entries t[0]...t[9], represents the integer
    t[0]+2^26 t[1]+2^51 t[2]+2^77 t[3]+2^102 t[4]+...+2^230 t[9].
    Bounds on each t[i] vary depending on context.
*/


typedef int32_t fe[10];


void fe_0(fe h);
void fe_1(fe h);

void fe_frombytes(fe h, const unsigned char *s);
void fe_tobytes(unsigned char *s, const fe h);

void fe_copy(fe h, const fe f);
int fe_isnegative(const fe f);
int fe_isnonzero(const fe f);
void fe_cmov(fe f, const fe g, unsigned int b);
void fe_cswap(fe f, fe g, unsigned int b);

void fe_neg(fe h, const fe f);
void fe_add(fe h, const fe f, const fe g);
void fe_invert(fe out, const fe z);
void fe_sq(fe h, const fe f);
void fe_sq2(fe h, const fe f);
void fe_mul(fe h, const fe f, const fe g);
void fe_mul121666(fe h, fe f);
void fe_pow22523(fe out, const fe z);
void fe_sub(fe h, const fe f, const fe g);

#endif


/*
  ge means group element.

  Here the group is the set of pairs (x,y) of field elements (see fe.h)
  satisfying -x^2 + y^2 = 1 + d x^2y^2
  where d = -121665/121666.
  
  Representations:
  ge_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z
  ge_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT
  ge_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T
  ge_precomp (Duif): (y+x,y-x,2dxy)
*/

typedef struct {
  fe X;
  fe Y;
  fe Z;
} ge_p2;

typedef struct {
  fe X;
  fe Y;
  fe Z;
  fe T;
} ge_p3;

typedef struct {
  fe X;
  fe Y;
  fe Z;
  fe T;
} ge_p1p1;

typedef struct {
  fe yplusx;
  fe yminusx;
  fe xy2d;
} ge_precomp;

typedef struct {
  fe YplusX;
  fe YminusX;
  fe Z;
  fe T2d;
} ge_cached;

void ge_p3_tobytes(unsigned char *s, const ge_p3 *h);
void ge_tobytes(unsigned char *s, const ge_p2 *h);
int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s);

void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q);
void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q);
void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b);
void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q);
void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q);
void ge_scalarmult_base(ge_p3 *h, const unsigned char *a);

void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p);
void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p);
void ge_p2_0(ge_p2 *h);
void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p);
void ge_p3_0(ge_p3 *h);
void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p);
void ge_p3_to_cached(ge_cached *r, const ge_p3 *p);
void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p);

#endif

//#include "src/sc.h"
#ifndef SC_H
#define SC_H

/*
The set of scalars is \Z/l
where l = 2^252 + 27742317777372353535851937790883648493.
*/

void sc_reduce(unsigned char *s);
void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c);

#endif

int lcled25519_verify(const uint8_t *sig,const uint8_t *msg,size_t len,const uint8_t *pub)
{
  return ed25519_verify(sig,msg,len,pub);
}

#ifdef FOOP
int ed2main() {
    unsigned char public_key[32], private_key[64], seed[32], scalar[32];
    unsigned char other_public_key[32], other_private_key[64];
    unsigned char shared_secret[32], other_shared_secret[32];
    unsigned char signature[64];

    clock_t start;
    clock_t end;
    int i;

    const unsigned char message[] = "Hello, world!";
    const int message_len = strlen((char*) message);

#ifdef FOOP
    /* create a random seed, and a keypair out of that seed */
    ed25519_create_seed(seed);
    ed25519_create_keypair(public_key, private_key, seed);

    /* create signature on the message with the keypair */
    ed25519_sign(signature, message, message_len, public_key, private_key);
#endif // FOOP

    /* verify the signature */
    if (ed25519_verify(signature, message, message_len, public_key)) {
        myprintf("valid signature\n");
    } else {
        myprintf("invalid signature\n");
    }

#ifdef FOOP
    /* create scalar and add it to the keypair */
    ed25519_create_seed(scalar);
    ed25519_add_scalar(public_key, private_key, scalar);

    /* create signature with the new keypair */
    ed25519_sign(signature, message, message_len, public_key, private_key);

    /* verify the signature with the new keypair */
    if (ed25519_verify(signature, message, message_len, public_key)) {
        myprintf("valid signature\n");
    } else {
        myprintf("invalid signature\n");
    }

    /* make a slight adjustment and verify again */
    signature[44] ^= 0x10;
    if (ed25519_verify(signature, message, message_len, public_key)) {
        myprintf("did not detect signature change\n");
    } else {
        myprintf("correctly detected signature change\n");
    }

    /* generate two keypairs for testing key exchange */
    ed25519_create_seed(seed);
    ed25519_create_keypair(public_key, private_key, seed);
    ed25519_create_seed(seed);
    ed25519_create_keypair(other_public_key, other_private_key, seed);

    /* create two shared secrets - from both perspectives - and check if they're equal */
    ed25519_key_exchange(shared_secret, other_public_key, private_key);
    ed25519_key_exchange(other_shared_secret, public_key, other_private_key);

    for (i = 0; i < 32; ++i) {
        if (shared_secret[i] != other_shared_secret[i]) {
            myprintf("key exchange was incorrect\n");
            break;
        }
    }

    if (i == 32) {
        myprintf("key exchange was correct\n");
    }

    /* test performance */
    myprintf("testing seed generation performance: ");
    start = clock();
    for (i = 0; i < 10000; ++i) {
        ed25519_create_seed(seed);
    }
    end = clock();

    myprintf("%fus per seed\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000);


    myprintf("testing key generation performance: ");
    start = clock();
    for (i = 0; i < 10000; ++i) {
        ed25519_create_keypair(public_key, private_key, seed);
    }
    end = clock();

    myprintf("%fus per keypair\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000);

    myprintf("testing sign performance: ");
    start = clock();
    for (i = 0; i < 10000; ++i) {
        ed25519_sign(signature, message, message_len, public_key, private_key);
    }
    end = clock();

    myprintf("%fus per signature\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000);

    myprintf("testing verify performance: ");
    start = clock();
    for (i = 0; i < 10000; ++i) {
        ed25519_verify(signature, message, message_len, public_key);
    }
    end = clock();

    myprintf("%fus per signature\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000);
    

    myprintf("testing keypair scalar addition performance: ");
    start = clock();
    for (i = 0; i < 10000; ++i) {
        ed25519_add_scalar(public_key, private_key, scalar);
    }
    end = clock();

    myprintf("%fus per keypair\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000);

    myprintf("testing public key scalar addition performance: ");
    start = clock();
    for (i = 0; i < 10000; ++i) {
        ed25519_add_scalar(public_key, NULL, scalar);
    }
    end = clock();

    myprintf("%fus per key\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000);

    myprintf("testing key exchange performance: ");
    start = clock();
    for (i = 0; i < 10000; ++i) {
        ed25519_key_exchange(shared_secret, other_public_key, private_key);
    }
    end = clock();

    myprintf("%fus per shared secret\n", ((double) ((end - start) * 1000)) / CLOCKS_PER_SEC / i * 1000);
#endif // FOOP

    return 0;
}
#endif // FOOP



/*
 src/sign.o src/verify.o src/keypair.o src/seed.o src/sc.o src/ge.o src/fe.o src/sha512.o
*/
//#include "ed25519.h"
//#include "src/sha512.h"
#ifndef SHA512_H
#define SHA512_H

//#include <stddef.h>

//#include "fixedint.h"

/* state */
typedef struct sha512_context_ {
    uint64_t  length, state[8];
    size_t curlen;
    unsigned char buf[128];
} sha512_context;

#ifdef FOOP
int sha512_init(sha512_context * md);
int sha512_final(sha512_context * md, unsigned char *out);
int sha512_update(sha512_context * md, const unsigned char *in, size_t inlen);
int sha512(const unsigned char *message, size_t message_len, unsigned char *out);
#endif // FOOP

#endif


/* LibTomCrypt, modular cryptographic library -- Tom St Denis
 *
 * LibTomCrypt is a library that provides various cryptographic
 * algorithms in a highly modular and flexible manner.
 *
 * The library is free for all purposes without any express
 * guarantee it works.
 *
 * Tom St Denis, tomstdenis@gmail.com, http://libtom.org
 */

//#include "fixedint.h"
//#include "sha512.h"

/* the K array */
static const uint64_t K[80] = {
    UINT64_C(0x428a2f98d728ae22), UINT64_C(0x7137449123ef65cd), 
    UINT64_C(0xb5c0fbcfec4d3b2f), UINT64_C(0xe9b5dba58189dbbc),
    UINT64_C(0x3956c25bf348b538), UINT64_C(0x59f111f1b605d019), 
    UINT64_C(0x923f82a4af194f9b), UINT64_C(0xab1c5ed5da6d8118),
    UINT64_C(0xd807aa98a3030242), UINT64_C(0x12835b0145706fbe), 
    UINT64_C(0x243185be4ee4b28c), UINT64_C(0x550c7dc3d5ffb4e2),
    UINT64_C(0x72be5d74f27b896f), UINT64_C(0x80deb1fe3b1696b1), 
    UINT64_C(0x9bdc06a725c71235), UINT64_C(0xc19bf174cf692694),
    UINT64_C(0xe49b69c19ef14ad2), UINT64_C(0xefbe4786384f25e3), 
    UINT64_C(0x0fc19dc68b8cd5b5), UINT64_C(0x240ca1cc77ac9c65),
    UINT64_C(0x2de92c6f592b0275), UINT64_C(0x4a7484aa6ea6e483), 
    UINT64_C(0x5cb0a9dcbd41fbd4), UINT64_C(0x76f988da831153b5),
    UINT64_C(0x983e5152ee66dfab), UINT64_C(0xa831c66d2db43210), 
    UINT64_C(0xb00327c898fb213f), UINT64_C(0xbf597fc7beef0ee4),
    UINT64_C(0xc6e00bf33da88fc2), UINT64_C(0xd5a79147930aa725), 
    UINT64_C(0x06ca6351e003826f), UINT64_C(0x142929670a0e6e70),
    UINT64_C(0x27b70a8546d22ffc), UINT64_C(0x2e1b21385c26c926), 
    UINT64_C(0x4d2c6dfc5ac42aed), UINT64_C(0x53380d139d95b3df),
    UINT64_C(0x650a73548baf63de), UINT64_C(0x766a0abb3c77b2a8), 
    UINT64_C(0x81c2c92e47edaee6), UINT64_C(0x92722c851482353b),
    UINT64_C(0xa2bfe8a14cf10364), UINT64_C(0xa81a664bbc423001),
    UINT64_C(0xc24b8b70d0f89791), UINT64_C(0xc76c51a30654be30),
    UINT64_C(0xd192e819d6ef5218), UINT64_C(0xd69906245565a910), 
    UINT64_C(0xf40e35855771202a), UINT64_C(0x106aa07032bbd1b8),
    UINT64_C(0x19a4c116b8d2d0c8), UINT64_C(0x1e376c085141ab53), 
    UINT64_C(0x2748774cdf8eeb99), UINT64_C(0x34b0bcb5e19b48a8),
    UINT64_C(0x391c0cb3c5c95a63), UINT64_C(0x4ed8aa4ae3418acb), 
    UINT64_C(0x5b9cca4f7763e373), UINT64_C(0x682e6ff3d6b2b8a3),
    UINT64_C(0x748f82ee5defb2fc), UINT64_C(0x78a5636f43172f60), 
    UINT64_C(0x84c87814a1f0ab72), UINT64_C(0x8cc702081a6439ec),
    UINT64_C(0x90befffa23631e28), UINT64_C(0xa4506cebde82bde9), 
    UINT64_C(0xbef9a3f7b2c67915), UINT64_C(0xc67178f2e372532b),
    UINT64_C(0xca273eceea26619c), UINT64_C(0xd186b8c721c0c207), 
    UINT64_C(0xeada7dd6cde0eb1e), UINT64_C(0xf57d4f7fee6ed178),
    UINT64_C(0x06f067aa72176fba), UINT64_C(0x0a637dc5a2c898a6), 
    UINT64_C(0x113f9804bef90dae), UINT64_C(0x1b710b35131c471b),
    UINT64_C(0x28db77f523047d84), UINT64_C(0x32caab7b40c72493), 
    UINT64_C(0x3c9ebe0a15c9bebc), UINT64_C(0x431d67c49c100d4c),
    UINT64_C(0x4cc5d4becb3e42b6), UINT64_C(0x597f299cfc657e2a), 
    UINT64_C(0x5fcb6fab3ad6faec), UINT64_C(0x6c44198c4a475817)
};

/* Various logical functions */

#define ROR64c(x, y) \
    ( ((((x)&UINT64_C(0xFFFFFFFFFFFFFFFF))>>((uint64_t)(y)&UINT64_C(63))) | \
      ((x)<<((uint64_t)(64-((y)&UINT64_C(63)))))) & UINT64_C(0xFFFFFFFFFFFFFFFF))

#define STORE64H(x, y)                                                                     \
   { (y)[0] = (unsigned char)(((x)>>56)&255); (y)[1] = (unsigned char)(((x)>>48)&255);     \
     (y)[2] = (unsigned char)(((x)>>40)&255); (y)[3] = (unsigned char)(((x)>>32)&255);     \
     (y)[4] = (unsigned char)(((x)>>24)&255); (y)[5] = (unsigned char)(((x)>>16)&255);     \
     (y)[6] = (unsigned char)(((x)>>8)&255); (y)[7] = (unsigned char)((x)&255); }

#define LOAD64H(x, y)                                                      \
   { x = (((uint64_t)((y)[0] & 255))<<56)|(((uint64_t)((y)[1] & 255))<<48) | \
         (((uint64_t)((y)[2] & 255))<<40)|(((uint64_t)((y)[3] & 255))<<32) | \
         (((uint64_t)((y)[4] & 255))<<24)|(((uint64_t)((y)[5] & 255))<<16) | \
         (((uint64_t)((y)[6] & 255))<<8)|(((uint64_t)((y)[7] & 255))); }


#define Ch(x,y,z)       (z ^ (x & (y ^ z)))
#define Maj(x,y,z)      (((x | y) & z) | (x & y)) 
#define S(x, n)         ROR64c(x, n)
#define R(x, n)         (((x) &UINT64_C(0xFFFFFFFFFFFFFFFF))>>((uint64_t)n))
#define Sigma0(x)       (S(x, 28) ^ S(x, 34) ^ S(x, 39))
#define Sigma1(x)       (S(x, 14) ^ S(x, 18) ^ S(x, 41))
#define Gamma0(x)       (S(x, 1) ^ S(x, 8) ^ R(x, 7))
#define Gamma1(x)       (S(x, 19) ^ S(x, 61) ^ R(x, 6))
#ifndef MIN
   #define MIN(x, y) ( ((x)<(y))?(x):(y) )
#endif

/* compress 1024-bits */
static int sha512_compress(sha512_context *md, unsigned char *buf)
{
  uint64_t S[8], W[80], t0, t1;
  int i;
  
  /* copy state into S */
  for (i = 0; i < 8; i++) {
    S[i] = md->state[i];
  }
  
  /* copy the state into 1024-bits into W[0..15] */
  for (i = 0; i < 16; i++) {
    LOAD64H(W[i], buf + (8*i));
  }
  
  /* fill W[16..79] */
  for (i = 16; i < 80; i++) {
    W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16];
  }        
  
  /* Compress */
#define RND(a,b,c,d,e,f,g,h,i)			    \
  t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i];   \
  t1 = Sigma0(a) + Maj(a, b, c);		    \
  d += t0;					    \
  h  = t0 + t1;
  
  for (i = 0; i < 80; i += 8) {
    RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],i+0);
    RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],i+1);
    RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],i+2);
    RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],i+3);
    RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],i+4);
    RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],i+5);
    RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],i+6);
    RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],i+7);
  }
  
#undef RND
    
  /* feedback */
  for (i = 0; i < 8; i++) {
    md->state[i] = md->state[i] + S[i];
  }
  
  return 0;
}


/**
   Initialize the hash state
   @param md   The hash state you wish to initialize
   @return 0 if successful
*/
int sha512_init(sha512_context * md) {
  if (md == NULL) return 1;
  
  md->curlen = 0;
  md->length = 0;
  md->state[0] = UINT64_C(0x6a09e667f3bcc908);
  md->state[1] = UINT64_C(0xbb67ae8584caa73b);
  md->state[2] = UINT64_C(0x3c6ef372fe94f82b);
  md->state[3] = UINT64_C(0xa54ff53a5f1d36f1);
  md->state[4] = UINT64_C(0x510e527fade682d1);
  md->state[5] = UINT64_C(0x9b05688c2b3e6c1f);
  md->state[6] = UINT64_C(0x1f83d9abfb41bd6b);
  md->state[7] = UINT64_C(0x5be0cd19137e2179);
  
  return 0;
}

/**
   Process a block of memory though the hash
   @param md     The hash state
   @param in     The data to hash
   @param inlen  The length of the data (octets)
   @return 0 if successful
*/
int sha512_update (sha512_context * md, const unsigned char *in, size_t inlen)               
{                                                                                           
  size_t n;
  size_t i;                                                                        
  int           err;     
  if (md == NULL) return 1;  
  if (in == NULL) return 1;                                                              
  if (md->curlen > sizeof(md->buf)) {                             
    return 1;                                                            
  }                                                                                       
  while (inlen > 0) {                                                                     
    if (md->curlen == 0 && inlen >= 128) {                           
      if ((err = sha512_compress (md, (unsigned char *)in)) != 0) {               
	return err;                                                                   
      }                                                                                
      md->length += 128 * 8;                                        
      in             += 128;                                                    
      inlen          -= 128;                                                    
    } else {                                                                            
      n = MIN(inlen, (128 - md->curlen));
      
      for (i = 0; i < n; i++) {
	md->buf[i + md->curlen] = in[i];
      }
      
      
      md->curlen += n;                                                     
      in             += n;                                                             
      inlen          -= n;                                                             
      if (md->curlen == 128) {                                      
	if ((err = sha512_compress (md, md->buf)) != 0) {            
	  return err;                                                                
	}                                                                             
	md->length += 8*128;                                       
	md->curlen = 0;                                                   
      }                                                                                
    }                                                                                    
  }                                                                                       
  return 0;                                                                        
}

/**
   Terminate the hash to get the digest
   @param md  The hash state
   @param out [out] The destination of the hash (64 bytes)
   @return 0 if successful
*/
int sha512_final(sha512_context * md, unsigned char *out)
{
  int i;
  
  if (md == NULL) return 1;
  if (out == NULL) return 1;
  
  if (md->curlen >= sizeof(md->buf)) {
    return 1;
  }
  
  /* increase the length of the message */
  md->length += md->curlen * UINT64_C(8);
  
  /* append the '1' bit */
  md->buf[md->curlen++] = (unsigned char)0x80;
  
  /* if the length is currently above 112 bytes we append zeros
   * then compress.  Then we can fall back to padding zeros and length
   * encoding like normal.
   */
  if (md->curlen > 112) {
    while (md->curlen < 128) {
      md->buf[md->curlen++] = (unsigned char)0;
    }
    sha512_compress(md, md->buf);
    md->curlen = 0;
  }
  
  /* pad upto 120 bytes of zeroes 
   * note: that from 112 to 120 is the 64 MSB of the length.  We assume that you won't hash
   * > 2^64 bits of data... :-)
   */
  while (md->curlen < 120) {
    md->buf[md->curlen++] = (unsigned char)0;
  }
  
  /* store length */
  STORE64H(md->length, md->buf+120);
  sha512_compress(md, md->buf);
  
  /* copy output */
  for (i = 0; i < 8; i++) {
    STORE64H(md->state[i], out+(8*i));
  }
  
  return 0;
}

int sha512(const unsigned char *message, size_t message_len, unsigned char *out)
{
  sha512_context ctx;
  int ret;
  if ((ret = sha512_init(&ctx))) return ret;
  if ((ret = sha512_update(&ctx, message, message_len))) return ret;
  if ((ret = sha512_final(&ctx, out))) return ret;
  return 0;
}





//#include "ge.h"
//#include "sc.h"


void ed25519_sign(unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key, const unsigned char *private_key) {
    sha512_context hash;
    unsigned char hram[64];
    unsigned char r[64];
    ge_p3 R;


    sha512_init(&hash);
    sha512_update(&hash, private_key + 32, 32);
    sha512_update(&hash, message, message_len);
    sha512_final(&hash, r);

    sc_reduce(r);
    ge_scalarmult_base(&R, r);
    ge_p3_tobytes(signature, &R);

    sha512_init(&hash);
    sha512_update(&hash, signature, 32);
    sha512_update(&hash, public_key, 32);
    sha512_update(&hash, message, message_len);
    sha512_final(&hash, hram);

    sc_reduce(hram);
    sc_muladd(signature + 32, hram, private_key, r);
}
//#include "ed25519.h"
//#include "sha512.h"
//#include "ge.h"
//#include "sc.h"

static int consttime_equal(const unsigned char *x, const unsigned char *y) {
    unsigned char r = 0;

    r = x[0] ^ y[0];
    #define edF(i) r |= x[i] ^ y[i]
    edF(1);
    edF(2);
    edF(3);
    edF(4);
    edF(5);
    edF(6);
    edF(7);
    edF(8);
    edF(9);
    edF(10);
    edF(11);
    edF(12);
    edF(13);
    edF(14);
    edF(15);
    edF(16);
    edF(17);
    edF(18);
    edF(19);
    edF(20);
    edF(21);
    edF(22);
    edF(23);
    edF(24);
    edF(25);
    edF(26);
    edF(27);
    edF(28);
    edF(29);
    edF(30);
    edF(31);
    #undef edF

    return !r;
}

int ed25519_verify(const unsigned char *signature, const unsigned char *message, size_t message_len, const unsigned char *public_key) {
    unsigned char h[64];
    unsigned char checker[32];
    sha512_context hash;
    ge_p3 A;
    ge_p2 R;

    if (signature[63] & 224) {
        return 0;
    }

    if (ge_frombytes_negate_vartime(&A, public_key) != 0) {
        return 0;
    }

    sha512_init(&hash);
    sha512_update(&hash, signature, 32);
    sha512_update(&hash, public_key, 32);
    sha512_update(&hash, message, message_len);
    sha512_final(&hash, h);
    
    sc_reduce(h);
    ge_double_scalarmult_vartime(&R, h, &A, signature + 32);
    ge_tobytes(checker, &R);

    if (!consttime_equal(checker, signature)) {
        return 0;
    }

    return 1;
}
//#include "ed25519.h"
//#include "sha512.h"
//#include "ge.h"
void ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key, const unsigned char *seed) {
    ge_p3 A;

    sha512(seed, 32, private_key);
    private_key[0] &= 248;
    private_key[31] &= 63;
    private_key[31] |= 64;

    ge_scalarmult_base(&A, private_key);
    ge_p3_tobytes(public_key, &A);
}

void lcl_ed25519_create_keypair(unsigned char *public_key, unsigned char *private_key) {
    ge_p3 A;

    private_key[0] &= 248;
    private_key[31] &= 63;
    private_key[31] |= 64;

    ge_scalarmult_base(&A, private_key);
    ge_p3_tobytes(public_key, &A);
}

//#include "ed25519.h"

#ifndef ED25519_NO_SEED

#ifdef FOOP
#ifdef _WIN32
#include <windows.h>
#include <wincrypt.h>
#else
#include <stdio.h>
#endif
#endif // FOOP

int ed25519_create_seed(unsigned char *seed) {
#ifdef _WIN32
    HCRYPTPROV prov;

    if (!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))  {
        return 1;
    }

    if (!CryptGenRandom(prov, 32, seed))  {
        CryptReleaseContext(prov, 0);
        return 1;
    }

    CryptReleaseContext(prov, 0);
#else
    FILE *f = fopen("/dev/urandom", "rb");

    if (f == NULL) {
        return 1;
    }

    fread(seed, 1, 32, f);
    fclose(f);
#endif

    return 0;
}

#endif // ED25519_NO_SEED

void ed25519_key_exchange(unsigned char *shared_secret, const unsigned char *public_key, const unsigned char *private_key) {
    unsigned char e[32];
    unsigned int i;
    
    fe x1;
    fe x2;
    fe z2;
    fe x3;
    fe z3;
    fe tmp0;
    fe tmp1;

    int pos;
    unsigned int swap;
    unsigned int b;

    /* copy the private key and make sure it's valid */
    for (i = 0; i < 32; ++i) {
        e[i] = private_key[i];
    }

    e[0] &= 248;
    e[31] &= 63;
    e[31] |= 64;

    /* unpack the public key and convert edwards to montgomery */
    /* due to CodesInChaos: montgomeryX = (edwardsY + 1)*inverse(1 - edwardsY) mod p */
    fe_frombytes(x1, public_key);
    fe_1(tmp1);
    fe_add(tmp0, x1, tmp1);
    fe_sub(tmp1, tmp1, x1);
    fe_invert(tmp1, tmp1);
    fe_mul(x1, tmp0, tmp1);

    fe_1(x2);
    fe_0(z2);
    fe_copy(x3, x1);
    fe_1(z3);

    swap = 0;
    for (pos = 254; pos >= 0; --pos) {
        b = e[pos / 8] >> (pos & 7);
        b &= 1;
        swap ^= b;
        fe_cswap(x2, x3, swap);
        fe_cswap(z2, z3, swap);
        swap = b;

        /* from montgomery.h */
        fe_sub(tmp0, x3, z3);
        fe_sub(tmp1, x2, z2);
        fe_add(x2, x2, z2);
        fe_add(z2, x3, z3);
        fe_mul(z3, tmp0, x2);
        fe_mul(z2, z2, tmp1);
        fe_sq(tmp0, tmp1);
        fe_sq(tmp1, x2);
        fe_add(x3, z3, z2);
        fe_sub(z2, z3, z2);
        fe_mul(x2, tmp1, tmp0);
        fe_sub(tmp1, tmp1, tmp0);
        fe_sq(z2, z2);
        fe_mul121666(z3, tmp1);
        fe_sq(x3, x3);
        fe_add(tmp0, tmp0, z3);
        fe_mul(z3, x1, z2);
        fe_mul(z2, tmp1, tmp0);
    }

    fe_cswap(x2, x3, swap);
    fe_cswap(z2, z3, swap);

    fe_invert(z2, z2);
    fe_mul(x2, x2, z2);
    fe_tobytes(shared_secret, x2);
}


#define FOOP_SC

#ifdef FOOP_SC

//#include "src/fixedint.h"
//#include "sc.h"

static uint64_t sc_load_3(const unsigned char *in) {
    uint64_t result;

    result = (uint64_t) in[0];
    result |= ((uint64_t) in[1]) << 8;
    result |= ((uint64_t) in[2]) << 16;

    return result;
}

static uint64_t sc_load_4(const unsigned char *in) {
    uint64_t result;

    result = (uint64_t) in[0];
    result |= ((uint64_t) in[1]) << 8;
    result |= ((uint64_t) in[2]) << 16;
    result |= ((uint64_t) in[3]) << 24;
    
    return result;
}

/*
Input:
  s[0]+256*s[1]+...+256^63*s[63] = s

Output:
  s[0]+256*s[1]+...+256^31*s[31] = s mod l
  where l = 2^252 + 27742317777372353535851937790883648493.
  Overwrites s in place.
*/

void sc_reduce(unsigned char *s) {
    int64_t s0 = 2097151 & sc_load_3(s);
    int64_t s1 = 2097151 & (sc_load_4(s + 2) >> 5);
    int64_t s2 = 2097151 & (sc_load_3(s + 5) >> 2);
    int64_t s3 = 2097151 & (sc_load_4(s + 7) >> 7);
    int64_t s4 = 2097151 & (sc_load_4(s + 10) >> 4);
    int64_t s5 = 2097151 & (sc_load_3(s + 13) >> 1);
    int64_t s6 = 2097151 & (sc_load_4(s + 15) >> 6);
    int64_t s7 = 2097151 & (sc_load_3(s + 18) >> 3);
    int64_t s8 = 2097151 & sc_load_3(s + 21);
    int64_t s9 = 2097151 & (sc_load_4(s + 23) >> 5);
    int64_t s10 = 2097151 & (sc_load_3(s + 26) >> 2);
    int64_t s11 = 2097151 & (sc_load_4(s + 28) >> 7);
    int64_t s12 = 2097151 & (sc_load_4(s + 31) >> 4);
    int64_t s13 = 2097151 & (sc_load_3(s + 34) >> 1);
    int64_t s14 = 2097151 & (sc_load_4(s + 36) >> 6);
    int64_t s15 = 2097151 & (sc_load_3(s + 39) >> 3);
    int64_t s16 = 2097151 & sc_load_3(s + 42);
    int64_t s17 = 2097151 & (sc_load_4(s + 44) >> 5);
    int64_t s18 = 2097151 & (sc_load_3(s + 47) >> 2);
    int64_t s19 = 2097151 & (sc_load_4(s + 49) >> 7);
    int64_t s20 = 2097151 & (sc_load_4(s + 52) >> 4);
    int64_t s21 = 2097151 & (sc_load_3(s + 55) >> 1);
    int64_t s22 = 2097151 & (sc_load_4(s + 57) >> 6);
    int64_t s23 = (sc_load_4(s + 60) >> 3);
    int64_t carry0;
    int64_t carry1;
    int64_t carry2;
    int64_t carry3;
    int64_t carry4;
    int64_t carry5;
    int64_t carry6;
    int64_t carry7;
    int64_t carry8;
    int64_t carry9;
    int64_t carry10;
    int64_t carry11;
    int64_t carry12;
    int64_t carry13;
    int64_t carry14;
    int64_t carry15;
    int64_t carry16;

    s11 += s23 * 666643;
    s12 += s23 * 470296;
    s13 += s23 * 654183;
    s14 -= s23 * 997805;
    s15 += s23 * 136657;
    s16 -= s23 * 683901;
    s23 = 0;
    s10 += s22 * 666643;
    s11 += s22 * 470296;
    s12 += s22 * 654183;
    s13 -= s22 * 997805;
    s14 += s22 * 136657;
    s15 -= s22 * 683901;
    s22 = 0;
    s9 += s21 * 666643;
    s10 += s21 * 470296;
    s11 += s21 * 654183;
    s12 -= s21 * 997805;
    s13 += s21 * 136657;
    s14 -= s21 * 683901;
    s21 = 0;
    s8 += s20 * 666643;
    s9 += s20 * 470296;
    s10 += s20 * 654183;
    s11 -= s20 * 997805;
    s12 += s20 * 136657;
    s13 -= s20 * 683901;
    s20 = 0;
    s7 += s19 * 666643;
    s8 += s19 * 470296;
    s9 += s19 * 654183;
    s10 -= s19 * 997805;
    s11 += s19 * 136657;
    s12 -= s19 * 683901;
    s19 = 0;
    s6 += s18 * 666643;
    s7 += s18 * 470296;
    s8 += s18 * 654183;
    s9 -= s18 * 997805;
    s10 += s18 * 136657;
    s11 -= s18 * 683901;
    s18 = 0;
    carry6 = (s6 + (1 << 20)) >> 21;
    s7 += carry6;
    s6 -= carry6 << 21;
    carry8 = (s8 + (1 << 20)) >> 21;
    s9 += carry8;
    s8 -= carry8 << 21;
    carry10 = (s10 + (1 << 20)) >> 21;
    s11 += carry10;
    s10 -= carry10 << 21;
    carry12 = (s12 + (1 << 20)) >> 21;
    s13 += carry12;
    s12 -= carry12 << 21;
    carry14 = (s14 + (1 << 20)) >> 21;
    s15 += carry14;
    s14 -= carry14 << 21;
    carry16 = (s16 + (1 << 20)) >> 21;
    s17 += carry16;
    s16 -= carry16 << 21;
    carry7 = (s7 + (1 << 20)) >> 21;
    s8 += carry7;
    s7 -= carry7 << 21;
    carry9 = (s9 + (1 << 20)) >> 21;
    s10 += carry9;
    s9 -= carry9 << 21;
    carry11 = (s11 + (1 << 20)) >> 21;
    s12 += carry11;
    s11 -= carry11 << 21;
    carry13 = (s13 + (1 << 20)) >> 21;
    s14 += carry13;
    s13 -= carry13 << 21;
    carry15 = (s15 + (1 << 20)) >> 21;
    s16 += carry15;
    s15 -= carry15 << 21;
    s5 += s17 * 666643;
    s6 += s17 * 470296;
    s7 += s17 * 654183;
    s8 -= s17 * 997805;
    s9 += s17 * 136657;
    s10 -= s17 * 683901;
    s17 = 0;
    s4 += s16 * 666643;
    s5 += s16 * 470296;
    s6 += s16 * 654183;
    s7 -= s16 * 997805;
    s8 += s16 * 136657;
    s9 -= s16 * 683901;
    s16 = 0;
    s3 += s15 * 666643;
    s4 += s15 * 470296;
    s5 += s15 * 654183;
    s6 -= s15 * 997805;
    s7 += s15 * 136657;
    s8 -= s15 * 683901;
    s15 = 0;
    s2 += s14 * 666643;
    s3 += s14 * 470296;
    s4 += s14 * 654183;
    s5 -= s14 * 997805;
    s6 += s14 * 136657;
    s7 -= s14 * 683901;
    s14 = 0;
    s1 += s13 * 666643;
    s2 += s13 * 470296;
    s3 += s13 * 654183;
    s4 -= s13 * 997805;
    s5 += s13 * 136657;
    s6 -= s13 * 683901;
    s13 = 0;
    s0 += s12 * 666643;
    s1 += s12 * 470296;
    s2 += s12 * 654183;
    s3 -= s12 * 997805;
    s4 += s12 * 136657;
    s5 -= s12 * 683901;
    s12 = 0;
    carry0 = (s0 + (1 << 20)) >> 21;
    s1 += carry0;
    s0 -= carry0 << 21;
    carry2 = (s2 + (1 << 20)) >> 21;
    s3 += carry2;
    s2 -= carry2 << 21;
    carry4 = (s4 + (1 << 20)) >> 21;
    s5 += carry4;
    s4 -= carry4 << 21;
    carry6 = (s6 + (1 << 20)) >> 21;
    s7 += carry6;
    s6 -= carry6 << 21;
    carry8 = (s8 + (1 << 20)) >> 21;
    s9 += carry8;
    s8 -= carry8 << 21;
    carry10 = (s10 + (1 << 20)) >> 21;
    s11 += carry10;
    s10 -= carry10 << 21;
    carry1 = (s1 + (1 << 20)) >> 21;
    s2 += carry1;
    s1 -= carry1 << 21;
    carry3 = (s3 + (1 << 20)) >> 21;
    s4 += carry3;
    s3 -= carry3 << 21;
    carry5 = (s5 + (1 << 20)) >> 21;
    s6 += carry5;
    s5 -= carry5 << 21;
    carry7 = (s7 + (1 << 20)) >> 21;
    s8 += carry7;
    s7 -= carry7 << 21;
    carry9 = (s9 + (1 << 20)) >> 21;
    s10 += carry9;
    s9 -= carry9 << 21;
    carry11 = (s11 + (1 << 20)) >> 21;
    s12 += carry11;
    s11 -= carry11 << 21;
    s0 += s12 * 666643;
    s1 += s12 * 470296;
    s2 += s12 * 654183;
    s3 -= s12 * 997805;
    s4 += s12 * 136657;
    s5 -= s12 * 683901;
    s12 = 0;
    carry0 = s0 >> 21;
    s1 += carry0;
    s0 -= carry0 << 21;
    carry1 = s1 >> 21;
    s2 += carry1;
    s1 -= carry1 << 21;
    carry2 = s2 >> 21;
    s3 += carry2;
    s2 -= carry2 << 21;
    carry3 = s3 >> 21;
    s4 += carry3;
    s3 -= carry3 << 21;
    carry4 = s4 >> 21;
    s5 += carry4;
    s4 -= carry4 << 21;
    carry5 = s5 >> 21;
    s6 += carry5;
    s5 -= carry5 << 21;
    carry6 = s6 >> 21;
    s7 += carry6;
    s6 -= carry6 << 21;
    carry7 = s7 >> 21;
    s8 += carry7;
    s7 -= carry7 << 21;
    carry8 = s8 >> 21;
    s9 += carry8;
    s8 -= carry8 << 21;
    carry9 = s9 >> 21;
    s10 += carry9;
    s9 -= carry9 << 21;
    carry10 = s10 >> 21;
    s11 += carry10;
    s10 -= carry10 << 21;
    carry11 = s11 >> 21;
    s12 += carry11;
    s11 -= carry11 << 21;
    s0 += s12 * 666643;
    s1 += s12 * 470296;
    s2 += s12 * 654183;
    s3 -= s12 * 997805;
    s4 += s12 * 136657;
    s5 -= s12 * 683901;
    s12 = 0;
    carry0 = s0 >> 21;
    s1 += carry0;
    s0 -= carry0 << 21;
    carry1 = s1 >> 21;
    s2 += carry1;
    s1 -= carry1 << 21;
    carry2 = s2 >> 21;
    s3 += carry2;
    s2 -= carry2 << 21;
    carry3 = s3 >> 21;
    s4 += carry3;
    s3 -= carry3 << 21;
    carry4 = s4 >> 21;
    s5 += carry4;
    s4 -= carry4 << 21;
    carry5 = s5 >> 21;
    s6 += carry5;
    s5 -= carry5 << 21;
    carry6 = s6 >> 21;
    s7 += carry6;
    s6 -= carry6 << 21;
    carry7 = s7 >> 21;
    s8 += carry7;
    s7 -= carry7 << 21;
    carry8 = s8 >> 21;
    s9 += carry8;
    s8 -= carry8 << 21;
    carry9 = s9 >> 21;
    s10 += carry9;
    s9 -= carry9 << 21;
    carry10 = s10 >> 21;
    s11 += carry10;
    s10 -= carry10 << 21;

    s[0] = (unsigned char) (s0 >> 0);
    s[1] = (unsigned char) (s0 >> 8);
    s[2] = (unsigned char) ((s0 >> 16) | (s1 << 5));
    s[3] = (unsigned char) (s1 >> 3);
    s[4] = (unsigned char) (s1 >> 11);
    s[5] = (unsigned char) ((s1 >> 19) | (s2 << 2));
    s[6] = (unsigned char) (s2 >> 6);
    s[7] = (unsigned char) ((s2 >> 14) | (s3 << 7));
    s[8] = (unsigned char) (s3 >> 1);
    s[9] = (unsigned char) (s3 >> 9);
    s[10] = (unsigned char) ((s3 >> 17) | (s4 << 4));
    s[11] = (unsigned char) (s4 >> 4);
    s[12] = (unsigned char) (s4 >> 12);
    s[13] = (unsigned char) ((s4 >> 20) | (s5 << 1));
    s[14] = (unsigned char) (s5 >> 7);
    s[15] = (unsigned char) ((s5 >> 15) | (s6 << 6));
    s[16] = (unsigned char) (s6 >> 2);
    s[17] = (unsigned char) (s6 >> 10);
    s[18] = (unsigned char) ((s6 >> 18) | (s7 << 3));
    s[19] = (unsigned char) (s7 >> 5);
    s[20] = (unsigned char) (s7 >> 13);
    s[21] = (unsigned char) (s8 >> 0);
    s[22] = (unsigned char) (s8 >> 8);
    s[23] = (unsigned char) ((s8 >> 16) | (s9 << 5));
    s[24] = (unsigned char) (s9 >> 3);
    s[25] = (unsigned char) (s9 >> 11);
    s[26] = (unsigned char) ((s9 >> 19) | (s10 << 2));
    s[27] = (unsigned char) (s10 >> 6);
    s[28] = (unsigned char) ((s10 >> 14) | (s11 << 7));
    s[29] = (unsigned char) (s11 >> 1);
    s[30] = (unsigned char) (s11 >> 9);
    s[31] = (unsigned char) (s11 >> 17);
}



/*
Input:
  a[0]+256*a[1]+...+256^31*a[31] = a
  b[0]+256*b[1]+...+256^31*b[31] = b
  c[0]+256*c[1]+...+256^31*c[31] = c

Output:
  s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l
  where l = 2^252 + 27742317777372353535851937790883648493.
*/

void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c) {
    int64_t a0 = 2097151 & sc_load_3(a);
    int64_t a1 = 2097151 & (sc_load_4(a + 2) >> 5);
    int64_t a2 = 2097151 & (sc_load_3(a + 5) >> 2);
    int64_t a3 = 2097151 & (sc_load_4(a + 7) >> 7);
    int64_t a4 = 2097151 & (sc_load_4(a + 10) >> 4);
    int64_t a5 = 2097151 & (sc_load_3(a + 13) >> 1);
    int64_t a6 = 2097151 & (sc_load_4(a + 15) >> 6);
    int64_t a7 = 2097151 & (sc_load_3(a + 18) >> 3);
    int64_t a8 = 2097151 & sc_load_3(a + 21);
    int64_t a9 = 2097151 & (sc_load_4(a + 23) >> 5);
    int64_t a10 = 2097151 & (sc_load_3(a + 26) >> 2);
    int64_t a11 = (sc_load_4(a + 28) >> 7);
    int64_t b0 = 2097151 & sc_load_3(b);
    int64_t b1 = 2097151 & (sc_load_4(b + 2) >> 5);
    int64_t b2 = 2097151 & (sc_load_3(b + 5) >> 2);
    int64_t b3 = 2097151 & (sc_load_4(b + 7) >> 7);
    int64_t b4 = 2097151 & (sc_load_4(b + 10) >> 4);
    int64_t b5 = 2097151 & (sc_load_3(b + 13) >> 1);
    int64_t b6 = 2097151 & (sc_load_4(b + 15) >> 6);
    int64_t b7 = 2097151 & (sc_load_3(b + 18) >> 3);
    int64_t b8 = 2097151 & sc_load_3(b + 21);
    int64_t b9 = 2097151 & (sc_load_4(b + 23) >> 5);
    int64_t b10 = 2097151 & (sc_load_3(b + 26) >> 2);
    int64_t b11 = (sc_load_4(b + 28) >> 7);
    int64_t c0 = 2097151 & sc_load_3(c);
    int64_t c1 = 2097151 & (sc_load_4(c + 2) >> 5);
    int64_t c2 = 2097151 & (sc_load_3(c + 5) >> 2);
    int64_t c3 = 2097151 & (sc_load_4(c + 7) >> 7);
    int64_t c4 = 2097151 & (sc_load_4(c + 10) >> 4);
    int64_t c5 = 2097151 & (sc_load_3(c + 13) >> 1);
    int64_t c6 = 2097151 & (sc_load_4(c + 15) >> 6);
    int64_t c7 = 2097151 & (sc_load_3(c + 18) >> 3);
    int64_t c8 = 2097151 & sc_load_3(c + 21);
    int64_t c9 = 2097151 & (sc_load_4(c + 23) >> 5);
    int64_t c10 = 2097151 & (sc_load_3(c + 26) >> 2);
    int64_t c11 = (sc_load_4(c + 28) >> 7);
    int64_t s0;
    int64_t s1;
    int64_t s2;
    int64_t s3;
    int64_t s4;
    int64_t s5;
    int64_t s6;
    int64_t s7;
    int64_t s8;
    int64_t s9;
    int64_t s10;
    int64_t s11;
    int64_t s12;
    int64_t s13;
    int64_t s14;
    int64_t s15;
    int64_t s16;
    int64_t s17;
    int64_t s18;
    int64_t s19;
    int64_t s20;
    int64_t s21;
    int64_t s22;
    int64_t s23;
    int64_t carry0;
    int64_t carry1;
    int64_t carry2;
    int64_t carry3;
    int64_t carry4;
    int64_t carry5;
    int64_t carry6;
    int64_t carry7;
    int64_t carry8;
    int64_t carry9;
    int64_t carry10;
    int64_t carry11;
    int64_t carry12;
    int64_t carry13;
    int64_t carry14;
    int64_t carry15;
    int64_t carry16;
    int64_t carry17;
    int64_t carry18;
    int64_t carry19;
    int64_t carry20;
    int64_t carry21;
    int64_t carry22;

    s0 = c0 + a0 * b0;
    s1 = c1 + a0 * b1 + a1 * b0;
    s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0;
    s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0;
    s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0;
    s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0;
    s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0;
    s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0;
    s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1 + a8 * b0;
    s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2 + a8 * b1 + a9 * b0;
    s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0;
    s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0;
    s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3 + a10 * b2 + a11 * b1;
    s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3 + a11 * b2;
    s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + a11 * b3;
    s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4;
    s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5;
    s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6;
    s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7;
    s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8;
    s20 = a9 * b11 + a10 * b10 + a11 * b9;
    s21 = a10 * b11 + a11 * b10;
    s22 = a11 * b11;
    s23 = 0;
    carry0 = (s0 + (1 << 20)) >> 21;
    s1 += carry0;
    s0 -= carry0 << 21;
    carry2 = (s2 + (1 << 20)) >> 21;
    s3 += carry2;
    s2 -= carry2 << 21;
    carry4 = (s4 + (1 << 20)) >> 21;
    s5 += carry4;
    s4 -= carry4 << 21;
    carry6 = (s6 + (1 << 20)) >> 21;
    s7 += carry6;
    s6 -= carry6 << 21;
    carry8 = (s8 + (1 << 20)) >> 21;
    s9 += carry8;
    s8 -= carry8 << 21;
    carry10 = (s10 + (1 << 20)) >> 21;
    s11 += carry10;
    s10 -= carry10 << 21;
    carry12 = (s12 + (1 << 20)) >> 21;
    s13 += carry12;
    s12 -= carry12 << 21;
    carry14 = (s14 + (1 << 20)) >> 21;
    s15 += carry14;
    s14 -= carry14 << 21;
    carry16 = (s16 + (1 << 20)) >> 21;
    s17 += carry16;
    s16 -= carry16 << 21;
    carry18 = (s18 + (1 << 20)) >> 21;
    s19 += carry18;
    s18 -= carry18 << 21;
    carry20 = (s20 + (1 << 20)) >> 21;
    s21 += carry20;
    s20 -= carry20 << 21;
    carry22 = (s22 + (1 << 20)) >> 21;
    s23 += carry22;
    s22 -= carry22 << 21;
    carry1 = (s1 + (1 << 20)) >> 21;
    s2 += carry1;
    s1 -= carry1 << 21;
    carry3 = (s3 + (1 << 20)) >> 21;
    s4 += carry3;
    s3 -= carry3 << 21;
    carry5 = (s5 + (1 << 20)) >> 21;
    s6 += carry5;
    s5 -= carry5 << 21;
    carry7 = (s7 + (1 << 20)) >> 21;
    s8 += carry7;
    s7 -= carry7 << 21;
    carry9 = (s9 + (1 << 20)) >> 21;
    s10 += carry9;
    s9 -= carry9 << 21;
    carry11 = (s11 + (1 << 20)) >> 21;
    s12 += carry11;
    s11 -= carry11 << 21;
    carry13 = (s13 + (1 << 20)) >> 21;
    s14 += carry13;
    s13 -= carry13 << 21;
    carry15 = (s15 + (1 << 20)) >> 21;
    s16 += carry15;
    s15 -= carry15 << 21;
    carry17 = (s17 + (1 << 20)) >> 21;
    s18 += carry17;
    s17 -= carry17 << 21;
    carry19 = (s19 + (1 << 20)) >> 21;
    s20 += carry19;
    s19 -= carry19 << 21;
    carry21 = (s21 + (1 << 20)) >> 21;
    s22 += carry21;
    s21 -= carry21 << 21;
    s11 += s23 * 666643;
    s12 += s23 * 470296;
    s13 += s23 * 654183;
    s14 -= s23 * 997805;
    s15 += s23 * 136657;
    s16 -= s23 * 683901;
    s23 = 0;
    s10 += s22 * 666643;
    s11 += s22 * 470296;
    s12 += s22 * 654183;
    s13 -= s22 * 997805;
    s14 += s22 * 136657;
    s15 -= s22 * 683901;
    s22 = 0;
    s9 += s21 * 666643;
    s10 += s21 * 470296;
    s11 += s21 * 654183;
    s12 -= s21 * 997805;
    s13 += s21 * 136657;
    s14 -= s21 * 683901;
    s21 = 0;
    s8 += s20 * 666643;
    s9 += s20 * 470296;
    s10 += s20 * 654183;
    s11 -= s20 * 997805;
    s12 += s20 * 136657;
    s13 -= s20 * 683901;
    s20 = 0;
    s7 += s19 * 666643;
    s8 += s19 * 470296;
    s9 += s19 * 654183;
    s10 -= s19 * 997805;
    s11 += s19 * 136657;
    s12 -= s19 * 683901;
    s19 = 0;
    s6 += s18 * 666643;
    s7 += s18 * 470296;
    s8 += s18 * 654183;
    s9 -= s18 * 997805;
    s10 += s18 * 136657;
    s11 -= s18 * 683901;
    s18 = 0;
    carry6 = (s6 + (1 << 20)) >> 21;
    s7 += carry6;
    s6 -= carry6 << 21;
    carry8 = (s8 + (1 << 20)) >> 21;
    s9 += carry8;
    s8 -= carry8 << 21;
    carry10 = (s10 + (1 << 20)) >> 21;
    s11 += carry10;
    s10 -= carry10 << 21;
    carry12 = (s12 + (1 << 20)) >> 21;
    s13 += carry12;
    s12 -= carry12 << 21;
    carry14 = (s14 + (1 << 20)) >> 21;
    s15 += carry14;
    s14 -= carry14 << 21;
    carry16 = (s16 + (1 << 20)) >> 21;
    s17 += carry16;
    s16 -= carry16 << 21;
    carry7 = (s7 + (1 << 20)) >> 21;
    s8 += carry7;
    s7 -= carry7 << 21;
    carry9 = (s9 + (1 << 20)) >> 21;
    s10 += carry9;
    s9 -= carry9 << 21;
    carry11 = (s11 + (1 << 20)) >> 21;
    s12 += carry11;
    s11 -= carry11 << 21;
    carry13 = (s13 + (1 << 20)) >> 21;
    s14 += carry13;
    s13 -= carry13 << 21;
    carry15 = (s15 + (1 << 20)) >> 21;
    s16 += carry15;
    s15 -= carry15 << 21;
    s5 += s17 * 666643;
    s6 += s17 * 470296;
    s7 += s17 * 654183;
    s8 -= s17 * 997805;
    s9 += s17 * 136657;
    s10 -= s17 * 683901;
    s17 = 0;
    s4 += s16 * 666643;
    s5 += s16 * 470296;
    s6 += s16 * 654183;
    s7 -= s16 * 997805;
    s8 += s16 * 136657;
    s9 -= s16 * 683901;
    s16 = 0;
    s3 += s15 * 666643;
    s4 += s15 * 470296;
    s5 += s15 * 654183;
    s6 -= s15 * 997805;
    s7 += s15 * 136657;
    s8 -= s15 * 683901;
    s15 = 0;
    s2 += s14 * 666643;
    s3 += s14 * 470296;
    s4 += s14 * 654183;
    s5 -= s14 * 997805;
    s6 += s14 * 136657;
    s7 -= s14 * 683901;
    s14 = 0;
    s1 += s13 * 666643;
    s2 += s13 * 470296;
    s3 += s13 * 654183;
    s4 -= s13 * 997805;
    s5 += s13 * 136657;
    s6 -= s13 * 683901;
    s13 = 0;
    s0 += s12 * 666643;
    s1 += s12 * 470296;
    s2 += s12 * 654183;
    s3 -= s12 * 997805;
    s4 += s12 * 136657;
    s5 -= s12 * 683901;
    s12 = 0;
    carry0 = (s0 + (1 << 20)) >> 21;
    s1 += carry0;
    s0 -= carry0 << 21;
    carry2 = (s2 + (1 << 20)) >> 21;
    s3 += carry2;
    s2 -= carry2 << 21;
    carry4 = (s4 + (1 << 20)) >> 21;
    s5 += carry4;
    s4 -= carry4 << 21;
    carry6 = (s6 + (1 << 20)) >> 21;
    s7 += carry6;
    s6 -= carry6 << 21;
    carry8 = (s8 + (1 << 20)) >> 21;
    s9 += carry8;
    s8 -= carry8 << 21;
    carry10 = (s10 + (1 << 20)) >> 21;
    s11 += carry10;
    s10 -= carry10 << 21;
    carry1 = (s1 + (1 << 20)) >> 21;
    s2 += carry1;
    s1 -= carry1 << 21;
    carry3 = (s3 + (1 << 20)) >> 21;
    s4 += carry3;
    s3 -= carry3 << 21;
    carry5 = (s5 + (1 << 20)) >> 21;
    s6 += carry5;
    s5 -= carry5 << 21;
    carry7 = (s7 + (1 << 20)) >> 21;
    s8 += carry7;
    s7 -= carry7 << 21;
    carry9 = (s9 + (1 << 20)) >> 21;
    s10 += carry9;
    s9 -= carry9 << 21;
    carry11 = (s11 + (1 << 20)) >> 21;
    s12 += carry11;
    s11 -= carry11 << 21;
    s0 += s12 * 666643;
    s1 += s12 * 470296;
    s2 += s12 * 654183;
    s3 -= s12 * 997805;
    s4 += s12 * 136657;
    s5 -= s12 * 683901;
    s12 = 0;
    carry0 = s0 >> 21;
    s1 += carry0;
    s0 -= carry0 << 21;
    carry1 = s1 >> 21;
    s2 += carry1;
    s1 -= carry1 << 21;
    carry2 = s2 >> 21;
    s3 += carry2;
    s2 -= carry2 << 21;
    carry3 = s3 >> 21;
    s4 += carry3;
    s3 -= carry3 << 21;
    carry4 = s4 >> 21;
    s5 += carry4;
    s4 -= carry4 << 21;
    carry5 = s5 >> 21;
    s6 += carry5;
    s5 -= carry5 << 21;
    carry6 = s6 >> 21;
    s7 += carry6;
    s6 -= carry6 << 21;
    carry7 = s7 >> 21;
    s8 += carry7;
    s7 -= carry7 << 21;
    carry8 = s8 >> 21;
    s9 += carry8;
    s8 -= carry8 << 21;
    carry9 = s9 >> 21;
    s10 += carry9;
    s9 -= carry9 << 21;
    carry10 = s10 >> 21;
    s11 += carry10;
    s10 -= carry10 << 21;
    carry11 = s11 >> 21;
    s12 += carry11;
    s11 -= carry11 << 21;
    s0 += s12 * 666643;
    s1 += s12 * 470296;
    s2 += s12 * 654183;
    s3 -= s12 * 997805;
    s4 += s12 * 136657;
    s5 -= s12 * 683901;
    s12 = 0;
    carry0 = s0 >> 21;
    s1 += carry0;
    s0 -= carry0 << 21;
    carry1 = s1 >> 21;
    s2 += carry1;
    s1 -= carry1 << 21;
    carry2 = s2 >> 21;
    s3 += carry2;
    s2 -= carry2 << 21;
    carry3 = s3 >> 21;
    s4 += carry3;
    s3 -= carry3 << 21;
    carry4 = s4 >> 21;
    s5 += carry4;
    s4 -= carry4 << 21;
    carry5 = s5 >> 21;
    s6 += carry5;
    s5 -= carry5 << 21;
    carry6 = s6 >> 21;
    s7 += carry6;
    s6 -= carry6 << 21;
    carry7 = s7 >> 21;
    s8 += carry7;
    s7 -= carry7 << 21;
    carry8 = s8 >> 21;
    s9 += carry8;
    s8 -= carry8 << 21;
    carry9 = s9 >> 21;
    s10 += carry9;
    s9 -= carry9 << 21;
    carry10 = s10 >> 21;
    s11 += carry10;
    s10 -= carry10 << 21;
    
    s[0] = (unsigned char) (s0 >> 0);
    s[1] = (unsigned char) (s0 >> 8);
    s[2] = (unsigned char) ((s0 >> 16) | (s1 << 5));
    s[3] = (unsigned char) (s1 >> 3);
    s[4] = (unsigned char) (s1 >> 11);
    s[5] = (unsigned char) ((s1 >> 19) | (s2 << 2));
    s[6] = (unsigned char) (s2 >> 6);
    s[7] = (unsigned char) ((s2 >> 14) | (s3 << 7));
    s[8] = (unsigned char) (s3 >> 1);
    s[9] = (unsigned char) (s3 >> 9);
    s[10] = (unsigned char) ((s3 >> 17) | (s4 << 4));
    s[11] = (unsigned char) (s4 >> 4);
    s[12] = (unsigned char) (s4 >> 12);
    s[13] = (unsigned char) ((s4 >> 20) | (s5 << 1));
    s[14] = (unsigned char) (s5 >> 7);
    s[15] = (unsigned char) ((s5 >> 15) | (s6 << 6));
    s[16] = (unsigned char) (s6 >> 2);
    s[17] = (unsigned char) (s6 >> 10);
    s[18] = (unsigned char) ((s6 >> 18) | (s7 << 3));
    s[19] = (unsigned char) (s7 >> 5);
    s[20] = (unsigned char) (s7 >> 13);
    s[21] = (unsigned char) (s8 >> 0);
    s[22] = (unsigned char) (s8 >> 8);
    s[23] = (unsigned char) ((s8 >> 16) | (s9 << 5));
    s[24] = (unsigned char) (s9 >> 3);
    s[25] = (unsigned char) (s9 >> 11);
    s[26] = (unsigned char) ((s9 >> 19) | (s10 << 2));
    s[27] = (unsigned char) (s10 >> 6);
    s[28] = (unsigned char) ((s10 >> 14) | (s11 << 7));
    s[29] = (unsigned char) (s11 >> 1);
    s[30] = (unsigned char) (s11 >> 9);
    s[31] = (unsigned char) (s11 >> 17);
}

#endif // FOOP_SC


#define FOOP_GE

#ifdef FOOP_GE
//#include "ge.h"
//#include "src/precomp_data.h"
static const ge_precomp Bi[8] = {
    {
        { 25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605 },
        { -12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378 },
        { -8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546 },
    },
    {
        { 15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024 },
        { 16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574 },
        { 30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357 },
    },
    {
        { 10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380 },
        { 4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306 },
        { 19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942 },
    },
    {
        { 5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766 },
        { -30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701 },
        { 28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300 },
    },
    {
        { -22518993, -6692182, 14201702, -8745502, -23510406, 8844726, 18474211, -1361450, -13062696, 13821877 },
        { -6455177, -7839871, 3374702, -4740862, -27098617, -10571707, 31655028, -7212327, 18853322, -14220951 },
        { 4566830, -12963868, -28974889, -12240689, -7602672, -2830569, -8514358, -10431137, 2207753, -3209784 },
    },
    {
        { -25154831, -4185821, 29681144, 7868801, -6854661, -9423865, -12437364, -663000, -31111463, -16132436 },
        { 25576264, -2703214, 7349804, -11814844, 16472782, 9300885, 3844789, 15725684, 171356, 6466918 },
        { 23103977, 13316479, 9739013, -16149481, 817875, -15038942, 8965339, -14088058, -30714912, 16193877 },
    },
    {
        { -33521811, 3180713, -2394130, 14003687, -16903474, -16270840, 17238398, 4729455, -18074513, 9256800 },
        { -25182317, -4174131, 32336398, 5036987, -21236817, 11360617, 22616405, 9761698, -19827198, 630305 },
        { -13720693, 2639453, -24237460, -7406481, 9494427, -5774029, -6554551, -15960994, -2449256, -14291300 },
    },
    {
        { -3151181, -5046075, 9282714, 6866145, -31907062, -863023, -18940575, 15033784, 25105118, -7894876 },
        { -24326370, 15950226, -31801215, -14592823, -11662737, -5090925, 1573892, -2625887, 2198790, -15804619 },
        { -3099351, 10324967, -2241613, 7453183, -5446979, -2735503, -13812022, -16236442, -32461234, -12290683 },
    },
};


/* base[i][j] = (j+1)*256^i*B */
static const ge_precomp base[32][8] = {
    {
        {
            { 25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605 },
            { -12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378 },
            { -8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546 },
        },
        {
            { -12815894, -12976347, -21581243, 11784320, -25355658, -2750717, -11717903, -3814571, -358445, -10211303 },
            { -21703237, 6903825, 27185491, 6451973, -29577724, -9554005, -15616551, 11189268, -26829678, -5319081 },
            { 26966642, 11152617, 32442495, 15396054, 14353839, -12752335, -3128826, -9541118, -15472047, -4166697 },
        },
        {
            { 15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024 },
            { 16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574 },
            { 30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357 },
        },
        {
            { -17036878, 13921892, 10945806, -6033431, 27105052, -16084379, -28926210, 15006023, 3284568, -6276540 },
            { 23599295, -8306047, -11193664, -7687416, 13236774, 10506355, 7464579, 9656445, 13059162, 10374397 },
            { 7798556, 16710257, 3033922, 2874086, 28997861, 2835604, 32406664, -3839045, -641708, -101325 },
        },
        {
            { 10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380 },
            { 4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306 },
            { 19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942 },
        },
        {
            { -15371964, -12862754, 32573250, 4720197, -26436522, 5875511, -19188627, -15224819, -9818940, -12085777 },
            { -8549212, 109983, 15149363, 2178705, 22900618, 4543417, 3044240, -15689887, 1762328, 14866737 },
            { -18199695, -15951423, -10473290, 1707278, -17185920, 3916101, -28236412, 3959421, 27914454, 4383652 },
        },
        {
            { 5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766 },
            { -30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701 },
            { 28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300 },
        },
        {
            { 14499471, -2729599, -33191113, -4254652, 28494862, 14271267, 30290735, 10876454, -33154098, 2381726 },
            { -7195431, -2655363, -14730155, 462251, -27724326, 3941372, -6236617, 3696005, -32300832, 15351955 },
            { 27431194, 8222322, 16448760, -3907995, -18707002, 11938355, -32961401, -2970515, 29551813, 10109425 },
        },
    },
    {
        {
            { -13657040, -13155431, -31283750, 11777098, 21447386, 6519384, -2378284, -1627556, 10092783, -4764171 },
            { 27939166, 14210322, 4677035, 16277044, -22964462, -12398139, -32508754, 12005538, -17810127, 12803510 },
            { 17228999, -15661624, -1233527, 300140, -1224870, -11714777, 30364213, -9038194, 18016357, 4397660 },
        },
        {
            { -10958843, -7690207, 4776341, -14954238, 27850028, -15602212, -26619106, 14544525, -17477504, 982639 },
            { 29253598, 15796703, -2863982, -9908884, 10057023, 3163536, 7332899, -4120128, -21047696, 9934963 },
            { 5793303, 16271923, -24131614, -10116404, 29188560, 1206517, -14747930, 4559895, -30123922, -10897950 },
        },
        {
            { -27643952, -11493006, 16282657, -11036493, 28414021, -15012264, 24191034, 4541697, -13338309, 5500568 },
            { 12650548, -1497113, 9052871, 11355358, -17680037, -8400164, -17430592, 12264343, 10874051, 13524335 },
            { 25556948, -3045990, 714651, 2510400, 23394682, -10415330, 33119038, 5080568, -22528059, 5376628 },
        },
        {
            { -26088264, -4011052, -17013699, -3537628, -6726793, 1920897, -22321305, -9447443, 4535768, 1569007 },
            { -2255422, 14606630, -21692440, -8039818, 28430649, 8775819, -30494562, 3044290, 31848280, 12543772 },
            { -22028579, 2943893, -31857513, 6777306, 13784462, -4292203, -27377195, -2062731, 7718482, 14474653 },
        },
        {
            { 2385315, 2454213, -22631320, 46603, -4437935, -15680415, 656965, -7236665, 24316168, -5253567 },
            { 13741529, 10911568, -33233417, -8603737, -20177830, -1033297, 33040651, -13424532, -20729456, 8321686 },
            { 21060490, -2212744, 15712757, -4336099, 1639040, 10656336, 23845965, -11874838, -9984458, 608372 },
        },
        {
            { -13672732, -15087586, -10889693, -7557059, -6036909, 11305547, 1123968, -6780577, 27229399, 23887 },
            { -23244140, -294205, -11744728, 14712571, -29465699, -2029617, 12797024, -6440308, -1633405, 16678954 },
            { -29500620, 4770662, -16054387, 14001338, 7830047, 9564805, -1508144, -4795045, -17169265, 4904953 },
        },
        {
            { 24059557, 14617003, 19037157, -15039908, 19766093, -14906429, 5169211, 16191880, 2128236, -4326833 },
            { -16981152, 4124966, -8540610, -10653797, 30336522, -14105247, -29806336, 916033, -6882542, -2986532 },
            { -22630907, 12419372, -7134229, -7473371, -16478904, 16739175, 285431, 2763829, 15736322, 4143876 },
        },
        {
            { 2379352, 11839345, -4110402, -5988665, 11274298, 794957, 212801, -14594663, 23527084, -16458268 },
            { 33431127, -11130478, -17838966, -15626900, 8909499, 8376530, -32625340, 4087881, -15188911, -14416214 },
            { 1767683, 7197987, -13205226, -2022635, -13091350, 448826, 5799055, 4357868, -4774191, -16323038 },
        },
    },
    {
        {
            { 6721966, 13833823, -23523388, -1551314, 26354293, -11863321, 23365147, -3949732, 7390890, 2759800 },
            { 4409041, 2052381, 23373853, 10530217, 7676779, -12885954, 21302353, -4264057, 1244380, -12919645 },
            { -4421239, 7169619, 4982368, -2957590, 30256825, -2777540, 14086413, 9208236, 15886429, 16489664 },
        },
        {
            { 1996075, 10375649, 14346367, 13311202, -6874135, -16438411, -13693198, 398369, -30606455, -712933 },
            { -25307465, 9795880, -2777414, 14878809, -33531835, 14780363, 13348553, 12076947, -30836462, 5113182 },
            { -17770784, 11797796, 31950843, 13929123, -25888302, 12288344, -30341101, -7336386, 13847711, 5387222 },
        },
        {
            { -18582163, -3416217, 17824843, -2340966, 22744343, -10442611, 8763061, 3617786, -19600662, 10370991 },
            { 20246567, -14369378, 22358229, -543712, 18507283, -10413996, 14554437, -8746092, 32232924, 16763880 },
            { 9648505, 10094563, 26416693, 14745928, -30374318, -6472621, 11094161, 15689506, 3140038, -16510092 },
        },
        {
            { -16160072, 5472695, 31895588, 4744994, 8823515, 10365685, -27224800, 9448613, -28774454, 366295 },
            { 19153450, 11523972, -11096490, -6503142, -24647631, 5420647, 28344573, 8041113, 719605, 11671788 },
            { 8678025, 2694440, -6808014, 2517372, 4964326, 11152271, -15432916, -15266516, 27000813, -10195553 },
        },
        {
            { -15157904, 7134312, 8639287, -2814877, -7235688, 10421742, 564065, 5336097, 6750977, -14521026 },
            { 11836410, -3979488, 26297894, 16080799, 23455045, 15735944, 1695823, -8819122, 8169720, 16220347 },
            { -18115838, 8653647, 17578566, -6092619, -8025777, -16012763, -11144307, -2627664, -5990708, -14166033 },
        },
        {
            { -23308498, -10968312, 15213228, -10081214, -30853605, -11050004, 27884329, 2847284, 2655861, 1738395 },
            { -27537433, -14253021, -25336301, -8002780, -9370762, 8129821, 21651608, -3239336, -19087449, -11005278 },
            { 1533110, 3437855, 23735889, 459276, 29970501, 11335377, 26030092, 5821408, 10478196, 8544890 },
        },
        {
            { 32173121, -16129311, 24896207, 3921497, 22579056, -3410854, 19270449, 12217473, 17789017, -3395995 },
            { -30552961, -2228401, -15578829, -10147201, 13243889, 517024, 15479401, -3853233, 30460520, 1052596 },
            { -11614875, 13323618, 32618793, 8175907, -15230173, 12596687, 27491595, -4612359, 3179268, -9478891 },
        },
        {
            { 31947069, -14366651, -4640583, -15339921, -15125977, -6039709, -14756777, -16411740, 19072640, -9511060 },
            { 11685058, 11822410, 3158003, -13952594, 33402194, -4165066, 5977896, -5215017, 473099, 5040608 },
            { -20290863, 8198642, -27410132, 11602123, 1290375, -2799760, 28326862, 1721092, -19558642, -3131606 },
        },
    },
    {
        {
            { 7881532, 10687937, 7578723, 7738378, -18951012, -2553952, 21820786, 8076149, -27868496, 11538389 },
            { -19935666, 3899861, 18283497, -6801568, -15728660, -11249211, 8754525, 7446702, -5676054, 5797016 },
            { -11295600, -3793569, -15782110, -7964573, 12708869, -8456199, 2014099, -9050574, -2369172, -5877341 },
        },
        {
            { -22472376, -11568741, -27682020, 1146375, 18956691, 16640559, 1192730, -3714199, 15123619, 10811505 },
            { 14352098, -3419715, -18942044, 10822655, 32750596, 4699007, -70363, 15776356, -28886779, -11974553 },
            { -28241164, -8072475, -4978962, -5315317, 29416931, 1847569, -20654173, -16484855, 4714547, -9600655 },
        },
        {
            { 15200332, 8368572, 19679101, 15970074, -31872674, 1959451, 24611599, -4543832, -11745876, 12340220 },
            { 12876937, -10480056, 33134381, 6590940, -6307776, 14872440, 9613953, 8241152, 15370987, 9608631 },
            { -4143277, -12014408, 8446281, -391603, 4407738, 13629032, -7724868, 15866074, -28210621, -8814099 },
        },
        {
            { 26660628, -15677655, 8393734, 358047, -7401291, 992988, -23904233, 858697, 20571223, 8420556 },
            { 14620715, 13067227, -15447274, 8264467, 14106269, 15080814, 33531827, 12516406, -21574435, -12476749 },
            { 236881, 10476226, 57258, -14677024, 6472998, 2466984, 17258519, 7256740, 8791136, 15069930 },
        },
        {
            { 1276410, -9371918, 22949635, -16322807, -23493039, -5702186, 14711875, 4874229, -30663140, -2331391 },
            { 5855666, 4990204, -13711848, 7294284, -7804282, 1924647, -1423175, -7912378, -33069337, 9234253 },
            { 20590503, -9018988, 31529744, -7352666, -2706834, 10650548, 31559055, -11609587, 18979186, 13396066 },
        },
        {
            { 24474287, 4968103, 22267082, 4407354, 24063882, -8325180, -18816887, 13594782, 33514650, 7021958 },
            { -11566906, -6565505, -21365085, 15928892, -26158305, 4315421, -25948728, -3916677, -21480480, 12868082 },
            { -28635013, 13504661, 19988037, -2132761, 21078225, 6443208, -21446107, 2244500, -12455797, -8089383 },
        },
        {
            { -30595528, 13793479, -5852820, 319136, -25723172, -6263899, 33086546, 8957937, -15233648, 5540521 },
            { -11630176, -11503902, -8119500, -7643073, 2620056, 1022908, -23710744, -1568984, -16128528, -14962807 },
            { 23152971, 775386, 27395463, 14006635, -9701118, 4649512, 1689819, 892185, -11513277, -15205948 },
        },
        {
            { 9770129, 9586738, 26496094, 4324120, 1556511, -3550024, 27453819, 4763127, -19179614, 5867134 },
            { -32765025, 1927590, 31726409, -4753295, 23962434, -16019500, 27846559, 5931263, -29749703, -16108455 },
            { 27461885, -2977536, 22380810, 1815854, -23033753, -3031938, 7283490, -15148073, -19526700, 7734629 },
        },
    },
    {
        {
            { -8010264, -9590817, -11120403, 6196038, 29344158, -13430885, 7585295, -3176626, 18549497, 15302069 },
            { -32658337, -6171222, -7672793, -11051681, 6258878, 13504381, 10458790, -6418461, -8872242, 8424746 },
            { 24687205, 8613276, -30667046, -3233545, 1863892, -1830544, 19206234, 7134917, -11284482, -828919 },
        },
        {
            { 11334899, -9218022, 8025293, 12707519, 17523892, -10476071, 10243738, -14685461, -5066034, 16498837 },
            { 8911542, 6887158, -9584260, -6958590, 11145641, -9543680, 17303925, -14124238, 6536641, 10543906 },
            { -28946384, 15479763, -17466835, 568876, -1497683, 11223454, -2669190, -16625574, -27235709, 8876771 },
        },
        {
            { -25742899, -12566864, -15649966, -846607, -33026686, -796288, -33481822, 15824474, -604426, -9039817 },
            { 10330056, 70051, 7957388, -9002667, 9764902, 15609756, 27698697, -4890037, 1657394, 3084098 },
            { 10477963, -7470260, 12119566, -13250805, 29016247, -5365589, 31280319, 14396151, -30233575, 15272409 },
        },
        {
            { -12288309, 3169463, 28813183, 16658753, 25116432, -5630466, -25173957, -12636138, -25014757, 1950504 },
            { -26180358, 9489187, 11053416, -14746161, -31053720, 5825630, -8384306, -8767532, 15341279, 8373727 },
            { 28685821, 7759505, -14378516, -12002860, -31971820, 4079242, 298136, -10232602, -2878207, 15190420 },
        },
        {
            { -32932876, 13806336, -14337485, -15794431, -24004620, 10940928, 8669718, 2742393, -26033313, -6875003 },
            { -1580388, -11729417, -25979658, -11445023, -17411874, -10912854, 9291594, -16247779, -12154742, 6048605 },
            { -30305315, 14843444, 1539301, 11864366, 20201677, 1900163, 13934231, 5128323, 11213262, 9168384 },
        },
        {
            { -26280513, 11007847, 19408960, -940758, -18592965, -4328580, -5088060, -11105150, 20470157, -16398701 },
            { -23136053, 9282192, 14855179, -15390078, -7362815, -14408560, -22783952, 14461608, 14042978, 5230683 },
            { 29969567, -2741594, -16711867, -8552442, 9175486, -2468974, 21556951, 3506042, -5933891, -12449708 },
        },
        {
            { -3144746, 8744661, 19704003, 4581278, -20430686, 6830683, -21284170, 8971513, -28539189, 15326563 },
            { -19464629, 10110288, -17262528, -3503892, -23500387, 1355669, -15523050, 15300988, -20514118, 9168260 },
            { -5353335, 4488613, -23803248, 16314347, 7780487, -15638939, -28948358, 9601605, 33087103, -9011387 },
        },
        {
            { -19443170, -15512900, -20797467, -12445323, -29824447, 10229461, -27444329, -15000531, -5996870, 15664672 },
            { 23294591, -16632613, -22650781, -8470978, 27844204, 11461195, 13099750, -2460356, 18151676, 13417686 },
            { -24722913, -4176517, -31150679, 5988919, -26858785, 6685065, 1661597, -12551441, 15271676, -15452665 },
        },
    },
    {
        {
            { 11433042, -13228665, 8239631, -5279517, -1985436, -725718, -18698764, 2167544, -6921301, -13440182 },
            { -31436171, 15575146, 30436815, 12192228, -22463353, 9395379, -9917708, -8638997, 12215110, 12028277 },
            { 14098400, 6555944, 23007258, 5757252, -15427832, -12950502, 30123440, 4617780, -16900089, -655628 },
        },
        {
            { -4026201, -15240835, 11893168, 13718664, -14809462, 1847385, -15819999, 10154009, 23973261, -12684474 },
            { -26531820, -3695990, -1908898, 2534301, -31870557, -16550355, 18341390, -11419951, 32013174, -10103539 },
            { -25479301, 10876443, -11771086, -14625140, -12369567, 1838104, 21911214, 6354752, 4425632, -837822 },
        },
        {
            { -10433389, -14612966, 22229858, -3091047, -13191166, 776729, -17415375, -12020462, 4725005, 14044970 },
            { 19268650, -7304421, 1555349, 8692754, -21474059, -9910664, 6347390, -1411784, -19522291, -16109756 },
            { -24864089, 12986008, -10898878, -5558584, -11312371, -148526, 19541418, 8180106, 9282262, 10282508 },
        },
        {
            { -26205082, 4428547, -8661196, -13194263, 4098402, -14165257, 15522535, 8372215, 5542595, -10702683 },
            { -10562541, 14895633, 26814552, -16673850, -17480754, -2489360, -2781891, 6993761, -18093885, 10114655 },
            { -20107055, -929418, 31422704, 10427861, -7110749, 6150669, -29091755, -11529146, 25953725, -106158 },
        },
        {
            { -4234397, -8039292, -9119125, 3046000, 2101609, -12607294, 19390020, 6094296, -3315279, 12831125 },
            { -15998678, 7578152, 5310217, 14408357, -33548620, -224739, 31575954, 6326196, 7381791, -2421839 },
            { -20902779, 3296811, 24736065, -16328389, 18374254, 7318640, 6295303, 8082724, -15362489, 12339664 },
        },
        {
            { 27724736, 2291157, 6088201, -14184798, 1792727, 5857634, 13848414, 15768922, 25091167, 14856294 },
            { -18866652, 8331043, 24373479, 8541013, -701998, -9269457, 12927300, -12695493, -22182473, -9012899 },
            { -11423429, -5421590, 11632845, 3405020, 30536730, -11674039, -27260765, 13866390, 30146206, 9142070 },
        },
        {
            { 3924129, -15307516, -13817122, -10054960, 12291820, -668366, -27702774, 9326384, -8237858, 4171294 },
            { -15921940, 16037937, 6713787, 16606682, -21612135, 2790944, 26396185, 3731949, 345228, -5462949 },
            { -21327538, 13448259, 25284571, 1143661, 20614966, -8849387, 2031539, -12391231, -16253183, -13582083 },
        },
        {
            { 31016211, -16722429, 26371392, -14451233, -5027349, 14854137, 17477601, 3842657, 28012650, -16405420 },
            { -5075835, 9368966, -8562079, -4600902, -15249953, 6970560, -9189873, 16292057, -8867157, 3507940 },
            { 29439664, 3537914, 23333589, 6997794, -17555561, -11018068, -15209202, -15051267, -9164929, 6580396 },
        },
    },
    {
        {
            { -12185861, -7679788, 16438269, 10826160, -8696817, -6235611, 17860444, -9273846, -2095802, 9304567 },
            { 20714564, -4336911, 29088195, 7406487, 11426967, -5095705, 14792667, -14608617, 5289421, -477127 },
            { -16665533, -10650790, -6160345, -13305760, 9192020, -1802462, 17271490, 12349094, 26939669, -3752294 },
        },
        {
            { -12889898, 9373458, 31595848, 16374215, 21471720, 13221525, -27283495, -12348559, -3698806, 117887 },
            { 22263325, -6560050, 3984570, -11174646, -15114008, -566785, 28311253, 5358056, -23319780, 541964 },
            { 16259219, 3261970, 2309254, -15534474, -16885711, -4581916, 24134070, -16705829, -13337066, -13552195 },
        },
        {
            { 9378160, -13140186, -22845982, -12745264, 28198281, -7244098, -2399684, -717351, 690426, 14876244 },
            { 24977353, -314384, -8223969, -13465086, 28432343, -1176353, -13068804, -12297348, -22380984, 6618999 },
            { -1538174, 11685646, 12944378, 13682314, -24389511, -14413193, 8044829, -13817328, 32239829, -5652762 },
        },
        {
            { -18603066, 4762990, -926250, 8885304, -28412480, -3187315, 9781647, -10350059, 32779359, 5095274 },
            { -33008130, -5214506, -32264887, -3685216, 9460461, -9327423, -24601656, 14506724, 21639561, -2630236 },
            { -16400943, -13112215, 25239338, 15531969, 3987758, -4499318, -1289502, -6863535, 17874574, 558605 },
        },
        {
            { -13600129, 10240081, 9171883, 16131053, -20869254, 9599700, 33499487, 5080151, 2085892, 5119761 },
            { -22205145, -2519528, -16381601, 414691, -25019550, 2170430, 30634760, -8363614, -31999993, -5759884 },
            { -6845704, 15791202, 8550074, -1312654, 29928809, -12092256, 27534430, -7192145, -22351378, 12961482 },
        },
        {
            { -24492060, -9570771, 10368194, 11582341, -23397293, -2245287, 16533930, 8206996, -30194652, -5159638 },
            { -11121496, -3382234, 2307366, 6362031, -135455, 8868177, -16835630, 7031275, 7589640, 8945490 },
            { -32152748, 8917967, 6661220, -11677616, -1192060, -15793393, 7251489, -11182180, 24099109, -14456170 },
        },
        {
            { 5019558, -7907470, 4244127, -14714356, -26933272, 6453165, -19118182, -13289025, -6231896, -10280736 },
            { 10853594, 10721687, 26480089, 5861829, -22995819, 1972175, -1866647, -10557898, -3363451, -6441124 },
            { -17002408, 5906790, 221599, -6563147, 7828208, -13248918, 24362661, -2008168, -13866408, 7421392 },
        },
        {
            { 8139927, -6546497, 32257646, -5890546, 30375719, 1886181, -21175108, 15441252, 28826358, -4123029 },
            { 6267086, 9695052, 7709135, -16603597, -32869068, -1886135, 14795160, -7840124, 13746021, -1742048 },
            { 28584902, 7787108, -6732942, -15050729, 22846041, -7571236, -3181936, -363524, 4771362, -8419958 },
        },
    },
    {
        {
            { 24949256, 6376279, -27466481, -8174608, -18646154, -9930606, 33543569, -12141695, 3569627, 11342593 },
            { 26514989, 4740088, 27912651, 3697550, 19331575, -11472339, 6809886, 4608608, 7325975, -14801071 },
            { -11618399, -14554430, -24321212, 7655128, -1369274, 5214312, -27400540, 10258390, -17646694, -8186692 },
        },
        {
            { 11431204, 15823007, 26570245, 14329124, 18029990, 4796082, -31446179, 15580664, 9280358, -3973687 },
            { -160783, -10326257, -22855316, -4304997, -20861367, -13621002, -32810901, -11181622, -15545091, 4387441 },
            { -20799378, 12194512, 3937617, -5805892, -27154820, 9340370, -24513992, 8548137, 20617071, -7482001 },
        },
        {
            { -938825, -3930586, -8714311, 16124718, 24603125, -6225393, -13775352, -11875822, 24345683, 10325460 },
            { -19855277, -1568885, -22202708, 8714034, 14007766, 6928528, 16318175, -1010689, 4766743, 3552007 },
            { -21751364, -16730916, 1351763, -803421, -4009670, 3950935, 3217514, 14481909, 10988822, -3994762 },
        },
        {
            { 15564307, -14311570, 3101243, 5684148, 30446780, -8051356, 12677127, -6505343, -8295852, 13296005 },
            { -9442290, 6624296, -30298964, -11913677, -4670981, -2057379, 31521204, 9614054, -30000824, 12074674 },
            { 4771191, -135239, 14290749, -13089852, 27992298, 14998318, -1413936, -1556716, 29832613, -16391035 },
        },
        {
            { 7064884, -7541174, -19161962, -5067537, -18891269, -2912736, 25825242, 5293297, -27122660, 13101590 },
            { -2298563, 2439670, -7466610, 1719965, -27267541, -16328445, 32512469, -5317593, -30356070, -4190957 },
            { -30006540, 10162316, -33180176, 3981723, -16482138, -13070044, 14413974, 9515896, 19568978, 9628812 },
        },
        {
            { 33053803, 199357, 15894591, 1583059, 27380243, -4580435, -17838894, -6106839, -6291786, 3437740 },
            { -18978877, 3884493, 19469877, 12726490, 15913552, 13614290, -22961733, 70104, 7463304, 4176122 },
            { -27124001, 10659917, 11482427, -16070381, 12771467, -6635117, -32719404, -5322751, 24216882, 5944158 },
        },
        {
            { 8894125, 7450974, -2664149, -9765752, -28080517, -12389115, 19345746, 14680796, 11632993, 5847885 },
            { 26942781, -2315317, 9129564, -4906607, 26024105, 11769399, -11518837, 6367194, -9727230, 4782140 },
            { 19916461, -4828410, -22910704, -11414391, 25606324, -5972441, 33253853, 8220911, 6358847, -1873857 },
        },
        {
            { 801428, -2081702, 16569428, 11065167, 29875704, 96627, 7908388, -4480480, -13538503, 1387155 },
            { 19646058, 5720633, -11416706, 12814209, 11607948, 12749789, 14147075, 15156355, -21866831, 11835260 },
            { 19299512, 1155910, 28703737, 14890794, 2925026, 7269399, 26121523, 15467869, -26560550, 5052483 },
        },
    },
    {
        {
            { -3017432, 10058206, 1980837, 3964243, 22160966, 12322533, -6431123, -12618185, 12228557, -7003677 },
            { 32944382, 14922211, -22844894, 5188528, 21913450, -8719943, 4001465, 13238564, -6114803, 8653815 },
            { 22865569, -4652735, 27603668, -12545395, 14348958, 8234005, 24808405, 5719875, 28483275, 2841751 },
        },
        {
            { -16420968, -1113305, -327719, -12107856, 21886282, -15552774, -1887966, -315658, 19932058, -12739203 },
            { -11656086, 10087521, -8864888, -5536143, -19278573, -3055912, 3999228, 13239134, -4777469, -13910208 },
            { 1382174, -11694719, 17266790, 9194690, -13324356, 9720081, 20403944, 11284705, -14013818, 3093230 },
        },
        {
            { 16650921, -11037932, -1064178, 1570629, -8329746, 7352753, -302424, 16271225, -24049421, -6691850 },
            { -21911077, -5927941, -4611316, -5560156, -31744103, -10785293, 24123614, 15193618, -21652117, -16739389 },
            { -9935934, -4289447, -25279823, 4372842, 2087473, 10399484, 31870908, 14690798, 17361620, 11864968 },
        },
        {
            { -11307610, 6210372, 13206574, 5806320, -29017692, -13967200, -12331205, -7486601, -25578460, -16240689 },
            { 14668462, -12270235, 26039039, 15305210, 25515617, 4542480, 10453892, 6577524, 9145645, -6443880 },
            { 5974874, 3053895, -9433049, -10385191, -31865124, 3225009, -7972642, 3936128, -5652273, -3050304 },
        },
        {
            { 30625386, -4729400, -25555961, -12792866, -20484575, 7695099, 17097188, -16303496, -27999779, 1803632 },
            { -3553091, 9865099, -5228566, 4272701, -5673832, -16689700, 14911344, 12196514, -21405489, 7047412 },
            { 20093277, 9920966, -11138194, -5343857, 13161587, 12044805, -32856851, 4124601, -32343828, -10257566 },
        },
        {
            { -20788824, 14084654, -13531713, 7842147, 19119038, -13822605, 4752377, -8714640, -21679658, 2288038 },
            { -26819236, -3283715, 29965059, 3039786, -14473765, 2540457, 29457502, 14625692, -24819617, 12570232 },
            { -1063558, -11551823, 16920318, 12494842, 1278292, -5869109, -21159943, -3498680, -11974704, 4724943 },
        },
        {
            { 17960970, -11775534, -4140968, -9702530, -8876562, -1410617, -12907383, -8659932, -29576300, 1903856 },
            { 23134274, -14279132, -10681997, -1611936, 20684485, 15770816, -12989750, 3190296, 26955097, 14109738 },
            { 15308788, 5320727, -30113809, -14318877, 22902008, 7767164, 29425325, -11277562, 31960942, 11934971 },
        },
        {
            { -27395711, 8435796, 4109644, 12222639, -24627868, 14818669, 20638173, 4875028, 10491392, 1379718 },
            { -13159415, 9197841, 3875503, -8936108, -1383712, -5879801, 33518459, 16176658, 21432314, 12180697 },
            { -11787308, 11500838, 13787581, -13832590, -22430679, 10140205, 1465425, 12689540, -10301319, -13872883 },
        },
    },
    {
        {
            { 5414091, -15386041, -21007664, 9643570, 12834970, 1186149, -2622916, -1342231, 26128231, 6032912 },
            { -26337395, -13766162, 32496025, -13653919, 17847801, -12669156, 3604025, 8316894, -25875034, -10437358 },
            { 3296484, 6223048, 24680646, -12246460, -23052020, 5903205, -8862297, -4639164, 12376617, 3188849 },
        },
        {
            { 29190488, -14659046, 27549113, -1183516, 3520066, -10697301, 32049515, -7309113, -16109234, -9852307 },
            { -14744486, -9309156, 735818, -598978, -20407687, -5057904, 25246078, -15795669, 18640741, -960977 },
            { -6928835, -16430795, 10361374, 5642961, 4910474, 12345252, -31638386, -494430, 10530747, 1053335 },
        },
        {
            { -29265967, -14186805, -13538216, -12117373, -19457059, -10655384, -31462369, -2948985, 24018831, 15026644 },
            { -22592535, -3145277, -2289276, 5953843, -13440189, 9425631, 25310643, 13003497, -2314791, -15145616 },
            { -27419985, -603321, -8043984, -1669117, -26092265, 13987819, -27297622, 187899, -23166419, -2531735 },
        },
        {
            { -21744398, -13810475, 1844840, 5021428, -10434399, -15911473, 9716667, 16266922, -5070217, 726099 },
            { 29370922, -6053998, 7334071, -15342259, 9385287, 2247707, -13661962, -4839461, 30007388, -15823341 },
            { -936379, 16086691, 23751945, -543318, -1167538, -5189036, 9137109, 730663, 9835848, 4555336 },
        },
        {
            { -23376435, 1410446, -22253753, -12899614, 30867635, 15826977, 17693930, 544696, -11985298, 12422646 },
            { 31117226, -12215734, -13502838, 6561947, -9876867, -12757670, -5118685, -4096706, 29120153, 13924425 },
            { -17400879, -14233209, 19675799, -2734756, -11006962, -5858820, -9383939, -11317700, 7240931, -237388 },
        },
        {
            { -31361739, -11346780, -15007447, -5856218, -22453340, -12152771, 1222336, 4389483, 3293637, -15551743 },
            { -16684801, -14444245, 11038544, 11054958, -13801175, -3338533, -24319580, 7733547, 12796905, -6335822 },
            { -8759414, -10817836, -25418864, 10783769, -30615557, -9746811, -28253339, 3647836, 3222231, -11160462 },
        },
        {
            { 18606113, 1693100, -25448386, -15170272, 4112353, 10045021, 23603893, -2048234, -7550776, 2484985 },
            { 9255317, -3131197, -12156162, -1004256, 13098013, -9214866, 16377220, -2102812, -19802075, -3034702 },
            { -22729289, 7496160, -5742199, 11329249, 19991973, -3347502, -31718148, 9936966, -30097688, -10618797 },
        },
        {
            { 21878590, -5001297, 4338336, 13643897, -3036865, 13160960, 19708896, 5415497, -7360503, -4109293 },
            { 27736861, 10103576, 12500508, 8502413, -3413016, -9633558, 10436918, -1550276, -23659143, -8132100 },
            { 19492550, -12104365, -29681976, -852630, -3208171, 12403437, 30066266, 8367329, 13243957, 8709688 },
        },
    },
    {
        {
            { 12015105, 2801261, 28198131, 10151021, 24818120, -4743133, -11194191, -5645734, 5150968, 7274186 },
            { 2831366, -12492146, 1478975, 6122054, 23825128, -12733586, 31097299, 6083058, 31021603, -9793610 },
            { -2529932, -2229646, 445613, 10720828, -13849527, -11505937, -23507731, 16354465, 15067285, -14147707 },
        },
        {
            { 7840942, 14037873, -33364863, 15934016, -728213, -3642706, 21403988, 1057586, -19379462, -12403220 },
            { 915865, -16469274, 15608285, -8789130, -24357026, 6060030, -17371319, 8410997, -7220461, 16527025 },
            { 32922597, -556987, 20336074, -16184568, 10903705, -5384487, 16957574, 52992, 23834301, 6588044 },
        },
        {
            { 32752030, 11232950, 3381995, -8714866, 22652988, -10744103, 17159699, 16689107, -20314580, -1305992 },
            { -4689649, 9166776, -25710296, -10847306, 11576752, 12733943, 7924251, -2752281, 1976123, -7249027 },
            { 21251222, 16309901, -2983015, -6783122, 30810597, 12967303, 156041, -3371252, 12331345, -8237197 },
        },
        {
            { 8651614, -4477032, -16085636, -4996994, 13002507, 2950805, 29054427, -5106970, 10008136, -4667901 },
            { 31486080, 15114593, -14261250, 12951354, 14369431, -7387845, 16347321, -13662089, 8684155, -10532952 },
            { 19443825, 11385320, 24468943, -9659068, -23919258, 2187569, -26263207, -6086921, 31316348, 14219878 },
        },
        {
            { -28594490, 1193785, 32245219, 11392485, 31092169, 15722801, 27146014, 6992409, 29126555, 9207390 },
            { 32382935, 1110093, 18477781, 11028262, -27411763, -7548111, -4980517, 10843782, -7957600, -14435730 },
            { 2814918, 7836403, 27519878, -7868156, -20894015, -11553689, -21494559, 8550130, 28346258, 1994730 },
        },
        {
            { -19578299, 8085545, -14000519, -3948622, 2785838, -16231307, -19516951, 7174894, 22628102, 8115180 },
            { -30405132, 955511, -11133838, -15078069, -32447087, -13278079, -25651578, 3317160, -9943017, 930272 },
            { -15303681, -6833769, 28856490, 1357446, 23421993, 1057177, 24091212, -1388970, -22765376, -10650715 },
        },
        {
            { -22751231, -5303997, -12907607, -12768866, -15811511, -7797053, -14839018, -16554220, -1867018, 8398970 },
            { -31969310, 2106403, -4736360, 1362501, 12813763, 16200670, 22981545, -6291273, 18009408, -15772772 },
            { -17220923, -9545221, -27784654, 14166835, 29815394, 7444469, 29551787, -3727419, 19288549, 1325865 },
        },
        {
            { 15100157, -15835752, -23923978, -1005098, -26450192, 15509408, 12376730, -3479146, 33166107, -8042750 },
            { 20909231, 13023121, -9209752, 16251778, -5778415, -8094914, 12412151, 10018715, 2213263, -13878373 },
            { 32529814, -11074689, 30361439, -16689753, -9135940, 1513226, 22922121, 6382134, -5766928, 8371348 },
        },
    },
    {
        {
            { 9923462, 11271500, 12616794, 3544722, -29998368, -1721626, 12891687, -8193132, -26442943, 10486144 },
            { -22597207, -7012665, 8587003, -8257861, 4084309, -12970062, 361726, 2610596, -23921530, -11455195 },
            { 5408411, -1136691, -4969122, 10561668, 24145918, 14240566, 31319731, -4235541, 19985175, -3436086 },
        },
        {
            { -13994457, 16616821, 14549246, 3341099, 32155958, 13648976, -17577068, 8849297, 65030, 8370684 },
            { -8320926, -12049626, 31204563, 5839400, -20627288, -1057277, -19442942, 6922164, 12743482, -9800518 },
            { -2361371, 12678785, 28815050, 4759974, -23893047, 4884717, 23783145, 11038569, 18800704, 255233 },
        },
        {
            { -5269658, -1773886, 13957886, 7990715, 23132995, 728773, 13393847, 9066957, 19258688, -14753793 },
            { -2936654, -10827535, -10432089, 14516793, -3640786, 4372541, -31934921, 2209390, -1524053, 2055794 },
            { 580882, 16705327, 5468415, -2683018, -30926419, -14696000, -7203346, -8994389, -30021019, 7394435 },
        },
        {
            { 23838809, 1822728, -15738443, 15242727, 8318092, -3733104, -21672180, -3492205, -4821741, 14799921 },
            { 13345610, 9759151, 3371034, -16137791, 16353039, 8577942, 31129804, 13496856, -9056018, 7402518 },
            { 2286874, -4435931, -20042458, -2008336, -13696227, 5038122, 11006906, -15760352, 8205061, 1607563 },
        },
        {
            { 14414086, -8002132, 3331830, -3208217, 22249151, -5594188, 18364661, -2906958, 30019587, -9029278 },
            { -27688051, 1585953, -10775053, 931069, -29120221, -11002319, -14410829, 12029093, 9944378, 8024 },
            { 4368715, -3709630, 29874200, -15022983, -20230386, -11410704, -16114594, -999085, -8142388, 5640030 },
        },
        {
            { 10299610, 13746483, 11661824, 16234854, 7630238, 5998374, 9809887, -16694564, 15219798, -14327783 },
            { 27425505, -5719081, 3055006, 10660664, 23458024, 595578, -15398605, -1173195, -18342183, 9742717 },
            { 6744077, 2427284, 26042789, 2720740, -847906, 1118974, 32324614, 7406442, 12420155, 1994844 },
        },
        {
            { 14012521, -5024720, -18384453, -9578469, -26485342, -3936439, -13033478, -10909803, 24319929, -6446333 },
            { 16412690, -4507367, 10772641, 15929391, -17068788, -4658621, 10555945, -10484049, -30102368, -4739048 },
            { 22397382, -7767684, -9293161, -12792868, 17166287, -9755136, -27333065, 6199366, 21880021, -12250760 },
        },
        {
            { -4283307, 5368523, -31117018, 8163389, -30323063, 3209128, 16557151, 8890729, 8840445, 4957760 },
            { -15447727, 709327, -6919446, -10870178, -29777922, 6522332, -21720181, 12130072, -14796503, 5005757 },
            { -2114751, -14308128, 23019042, 15765735, -25269683, 6002752, 10183197, -13239326, -16395286, -2176112 },
        },
    },
    {
        {
            { -19025756, 1632005, 13466291, -7995100, -23640451, 16573537, -32013908, -3057104, 22208662, 2000468 },
            { 3065073, -1412761, -25598674, -361432, -17683065, -5703415, -8164212, 11248527, -3691214, -7414184 },
            { 10379208, -6045554, 8877319, 1473647, -29291284, -12507580, 16690915, 2553332, -3132688, 16400289 },
        },
        {
            { 15716668, 1254266, -18472690, 7446274, -8448918, 6344164, -22097271, -7285580, 26894937, 9132066 },
            { 24158887, 12938817, 11085297, -8177598, -28063478, -4457083, -30576463, 64452, -6817084, -2692882 },
            { 13488534, 7794716, 22236231, 5989356, 25426474, -12578208, 2350710, -3418511, -4688006, 2364226 },
        },
        {
            { 16335052, 9132434, 25640582, 6678888, 1725628, 8517937, -11807024, -11697457, 15445875, -7798101 },
            { 29004207, -7867081, 28661402, -640412, -12794003, -7943086, 31863255, -4135540, -278050, -15759279 },
            { -6122061, -14866665, -28614905, 14569919, -10857999, -3591829, 10343412, -6976290, -29828287, -10815811 },
        },
        {
            { 27081650, 3463984, 14099042, -4517604, 1616303, -6205604, 29542636, 15372179, 17293797, 960709 },
            { 20263915, 11434237, -5765435, 11236810, 13505955, -10857102, -16111345, 6493122, -19384511, 7639714 },
            { -2830798, -14839232, 25403038, -8215196, -8317012, -16173699, 18006287, -16043750, 29994677, -15808121 },
        },
        {
            { 9769828, 5202651, -24157398, -13631392, -28051003, -11561624, -24613141, -13860782, -31184575, 709464 },
            { 12286395, 13076066, -21775189, -1176622, -25003198, 4057652, -32018128, -8890874, 16102007, 13205847 },
            { 13733362, 5599946, 10557076, 3195751, -5557991, 8536970, -25540170, 8525972, 10151379, 10394400 },
        },
        {
            { 4024660, -16137551, 22436262, 12276534, -9099015, -2686099, 19698229, 11743039, -33302334, 8934414 },
            { -15879800, -4525240, -8580747, -2934061, 14634845, -698278, -9449077, 3137094, -11536886, 11721158 },
            { 17555939, -5013938, 8268606, 2331751, -22738815, 9761013, 9319229, 8835153, -9205489, -1280045 },
        },
        {
            { -461409, -7830014, 20614118, 16688288, -7514766, -4807119, 22300304, 505429, 6108462, -6183415 },
            { -5070281, 12367917, -30663534, 3234473, 32617080, -8422642, 29880583, -13483331, -26898490, -7867459 },
            { -31975283, 5726539, 26934134, 10237677, -3173717, -605053, 24199304, 3795095, 7592688, -14992079 },
        },
        {
            { 21594432, -14964228, 17466408, -4077222, 32537084, 2739898, 6407723, 12018833, -28256052, 4298412 },
            { -20650503, -11961496, -27236275, 570498, 3767144, -1717540, 13891942, -1569194, 13717174, 10805743 },
            { -14676630, -15644296, 15287174, 11927123, 24177847, -8175568, -796431, 14860609, -26938930, -5863836 },
        },
    },
    {
        {
            { 12962541, 5311799, -10060768, 11658280, 18855286, -7954201, 13286263, -12808704, -4381056, 9882022 },
            { 18512079, 11319350, -20123124, 15090309, 18818594, 5271736, -22727904, 3666879, -23967430, -3299429 },
            { -6789020, -3146043, 16192429, 13241070, 15898607, -14206114, -10084880, -6661110, -2403099, 5276065 },
        },
        {
            { 30169808, -5317648, 26306206, -11750859, 27814964, 7069267, 7152851, 3684982, 1449224, 13082861 },
            { 10342826, 3098505, 2119311, 193222, 25702612, 12233820, 23697382, 15056736, -21016438, -8202000 },
            { -33150110, 3261608, 22745853, 7948688, 19370557, -15177665, -26171976, 6482814, -10300080, -11060101 },
        },
        {
            { 32869458, -5408545, 25609743, 15678670, -10687769, -15471071, 26112421, 2521008, -22664288, 6904815 },
            { 29506923, 4457497, 3377935, -9796444, -30510046, 12935080, 1561737, 3841096, -29003639, -6657642 },
            { 10340844, -6630377, -18656632, -2278430, 12621151, -13339055, 30878497, -11824370, -25584551, 5181966 },
        },
        {
            { 25940115, -12658025, 17324188, -10307374, -8671468, 15029094, 24396252, -16450922, -2322852, -12388574 },
            { -21765684, 9916823, -1300409, 4079498, -1028346, 11909559, 1782390, 12641087, 20603771, -6561742 },
            { -18882287, -11673380, 24849422, 11501709, 13161720, -4768874, 1925523, 11914390, 4662781, 7820689 },
        },
        {
            { 12241050, -425982, 8132691, 9393934, 32846760, -1599620, 29749456, 12172924, 16136752, 15264020 },
            { -10349955, -14680563, -8211979, 2330220, -17662549, -14545780, 10658213, 6671822, 19012087, 3772772 },
            { 3753511, -3421066, 10617074, 2028709, 14841030, -6721664, 28718732, -15762884, 20527771, 12988982 },
        },
        {
            { -14822485, -5797269, -3707987, 12689773, -898983, -10914866, -24183046, -10564943, 3299665, -12424953 },
            { -16777703, -15253301, -9642417, 4978983, 3308785, 8755439, 6943197, 6461331, -25583147, 8991218 },
            { -17226263, 1816362, -1673288, -6086439, 31783888, -8175991, -32948145, 7417950, -30242287, 1507265 },
        },
        {
            { 29692663, 6829891, -10498800, 4334896, 20945975, -11906496, -28887608, 8209391, 14606362, -10647073 },
            { -3481570, 8707081, 32188102, 5672294, 22096700, 1711240, -33020695, 9761487, 4170404, -2085325 },
            { -11587470, 14855945, -4127778, -1531857, -26649089, 15084046, 22186522, 16002000, -14276837, -8400798 },
        },
        {
            { -4811456, 13761029, -31703877, -2483919, -3312471, 7869047, -7113572, -9620092, 13240845, 10965870 },
            { -7742563, -8256762, -14768334, -13656260, -23232383, 12387166, 4498947, 14147411, 29514390, 4302863 },
            { -13413405, -12407859, 20757302, -13801832, 14785143, 8976368, -5061276, -2144373, 17846988, -13971927 },
        },
    },
    {
        {
            { -2244452, -754728, -4597030, -1066309, -6247172, 1455299, -21647728, -9214789, -5222701, 12650267 },
            { -9906797, -16070310, 21134160, 12198166, -27064575, 708126, 387813, 13770293, -19134326, 10958663 },
            { 22470984, 12369526, 23446014, -5441109, -21520802, -9698723, -11772496, -11574455, -25083830, 4271862 },
        },
        {
            { -25169565, -10053642, -19909332, 15361595, -5984358, 2159192, 75375, -4278529, -32526221, 8469673 },
            { 15854970, 4148314, -8893890, 7259002, 11666551, 13824734, -30531198, 2697372, 24154791, -9460943 },
            { 15446137, -15806644, 29759747, 14019369, 30811221, -9610191, -31582008, 12840104, 24913809, 9815020 },
        },
        {
            { -4709286, -5614269, -31841498, -12288893, -14443537, 10799414, -9103676, 13438769, 18735128, 9466238 },
            { 11933045, 9281483, 5081055, -5183824, -2628162, -4905629, -7727821, -10896103, -22728655, 16199064 },
            { 14576810, 379472, -26786533, -8317236, -29426508, -10812974, -102766, 1876699, 30801119, 2164795 },
        },
        {
            { 15995086, 3199873, 13672555, 13712240, -19378835, -4647646, -13081610, -15496269, -13492807, 1268052 },
            { -10290614, -3659039, -3286592, 10948818, 23037027, 3794475, -3470338, -12600221, -17055369, 3565904 },
            { 29210088, -9419337, -5919792, -4952785, 10834811, -13327726, -16512102, -10820713, -27162222, -14030531 },
        },
        {
            { -13161890, 15508588, 16663704, -8156150, -28349942, 9019123, -29183421, -3769423, 2244111, -14001979 },
            { -5152875, -3800936, -9306475, -6071583, 16243069, 14684434, -25673088, -16180800, 13491506, 4641841 },
            { 10813417, 643330, -19188515, -728916, 30292062, -16600078, 27548447, -7721242, 14476989, -12767431 },
        },
        {
            { 10292079, 9984945, 6481436, 8279905, -7251514, 7032743, 27282937, -1644259, -27912810, 12651324 },
            { -31185513, -813383, 22271204, 11835308, 10201545, 15351028, 17099662, 3988035, 21721536, -3148940 },
            { 10202177, -6545839, -31373232, -9574638, -32150642, -8119683, -12906320, 3852694, 13216206, 14842320 },
        },
        {
            { -15815640, -10601066, -6538952, -7258995, -6984659, -6581778, -31500847, 13765824, -27434397, 9900184 },
            { 14465505, -13833331, -32133984, -14738873, -27443187, 12990492, 33046193, 15796406, -7051866, -8040114 },
            { 30924417, -8279620, 6359016, -12816335, 16508377, 9071735, -25488601, 15413635, 9524356, -7018878 },
        },
        {
            { 12274201, -13175547, 32627641, -1785326, 6736625, 13267305, 5237659, -5109483, 15663516, 4035784 },
            { -2951309, 8903985, 17349946, 601635, -16432815, -4612556, -13732739, -15889334, -22258478, 4659091 },
            { -16916263, -4952973, -30393711, -15158821, 20774812, 15897498, 5736189, 15026997, -2178256, -13455585 },
        },
    },
    {
        {
            { -8858980, -2219056, 28571666, -10155518, -474467, -10105698, -3801496, 278095, 23440562, -290208 },
            { 10226241, -5928702, 15139956, 120818, -14867693, 5218603, 32937275, 11551483, -16571960, -7442864 },
            { 17932739, -12437276, -24039557, 10749060, 11316803, 7535897, 22503767, 5561594, -3646624, 3898661 },
        },
        {
            { 7749907, -969567, -16339731, -16464, -25018111, 15122143, -1573531, 7152530, 21831162, 1245233 },
            { 26958459, -14658026, 4314586, 8346991, -5677764, 11960072, -32589295, -620035, -30402091, -16716212 },
            { -12165896, 9166947, 33491384, 13673479, 29787085, 13096535, 6280834, 14587357, -22338025, 13987525 },
        },
        {
            { -24349909, 7778775, 21116000, 15572597, -4833266, -5357778, -4300898, -5124639, -7469781, -2858068 },
            { 9681908, -6737123, -31951644, 13591838, -6883821, 386950, 31622781, 6439245, -14581012, 4091397 },
            { -8426427, 1470727, -28109679, -1596990, 3978627, -5123623, -19622683, 12092163, 29077877, -14741988 },
        },
        {
            { 5269168, -6859726, -13230211, -8020715, 25932563, 1763552, -5606110, -5505881, -20017847, 2357889 },
            { 32264008, -15407652, -5387735, -1160093, -2091322, -3946900, 23104804, -12869908, 5727338, 189038 },
            { 14609123, -8954470, -6000566, -16622781, -14577387, -7743898, -26745169, 10942115, -25888931, -14884697 },
        },
        {
            { 20513500, 5557931, -15604613, 7829531, 26413943, -2019404, -21378968, 7471781, 13913677, -5137875 },
            { -25574376, 11967826, 29233242, 12948236, -6754465, 4713227, -8940970, 14059180, 12878652, 8511905 },
            { -25656801, 3393631, -2955415, -7075526, -2250709, 9366908, -30223418, 6812974, 5568676, -3127656 },
        },
        {
            { 11630004, 12144454, 2116339, 13606037, 27378885, 15676917, -17408753, -13504373, -14395196, 8070818 },
            { 27117696, -10007378, -31282771, -5570088, 1127282, 12772488, -29845906, 10483306, -11552749, -1028714 },
            { 10637467, -5688064, 5674781, 1072708, -26343588, -6982302, -1683975, 9177853, -27493162, 15431203 },
        },
        {
            { 20525145, 10892566, -12742472, 12779443, -29493034, 16150075, -28240519, 14943142, -15056790, -7935931 },
            { -30024462, 5626926, -551567, -9981087, 753598, 11981191, 25244767, -3239766, -3356550, 9594024 },
            { -23752644, 2636870, -5163910, -10103818, 585134, 7877383, 11345683, -6492290, 13352335, -10977084 },
        },
        {
            { -1931799, -5407458, 3304649, -12884869, 17015806, -4877091, -29783850, -7752482, -13215537, -319204 },
            { 20239939, 6607058, 6203985, 3483793, -18386976, -779229, -20723742, 15077870, -22750759, 14523817 },
            { 27406042, -6041657, 27423596, -4497394, 4996214, 10002360, -28842031, -4545494, -30172742, -4805667 },
        },
    },
    {
        {
            { 11374242, 12660715, 17861383, -12540833, 10935568, 1099227, -13886076, -9091740, -27727044, 11358504 },
            { -12730809, 10311867, 1510375, 10778093, -2119455, -9145702, 32676003, 11149336, -26123651, 4985768 },
            { -19096303, 341147, -6197485, -239033, 15756973, -8796662, -983043, 13794114, -19414307, -15621255 },
        },
        {
            { 6490081, 11940286, 25495923, -7726360, 8668373, -8751316, 3367603, 6970005, -1691065, -9004790 },
            { 1656497, 13457317, 15370807, 6364910, 13605745, 8362338, -19174622, -5475723, -16796596, -5031438 },
            { -22273315, -13524424, -64685, -4334223, -18605636, -10921968, -20571065, -7007978, -99853, -10237333 },
        },
        {
            { 17747465, 10039260, 19368299, -4050591, -20630635, -16041286, 31992683, -15857976, -29260363, -5511971 },
            { 31932027, -4986141, -19612382, 16366580, 22023614, 88450, 11371999, -3744247, 4882242, -10626905 },
            { 29796507, 37186, 19818052, 10115756, -11829032, 3352736, 18551198, 3272828, -5190932, -4162409 },
        },
        {
            { 12501286, 4044383, -8612957, -13392385, -32430052, 5136599, -19230378, -3529697, 330070, -3659409 },
            { 6384877, 2899513, 17807477, 7663917, -2358888, 12363165, 25366522, -8573892, -271295, 12071499 },
            { -8365515, -4042521, 25133448, -4517355, -6211027, 2265927, -32769618, 1936675, -5159697, 3829363 },
        },
        {
            { 28425966, -5835433, -577090, -4697198, -14217555, 6870930, 7921550, -6567787, 26333140, 14267664 },
            { -11067219, 11871231, 27385719, -10559544, -4585914, -11189312, 10004786, -8709488, -21761224, 8930324 },
            { -21197785, -16396035, 25654216, -1725397, 12282012, 11008919, 1541940, 4757911, -26491501, -16408940 },
        },
        {
            { 13537262, -7759490, -20604840, 10961927, -5922820, -13218065, -13156584, 6217254, -15943699, 13814990 },
            { -17422573, 15157790, 18705543, 29619, 24409717, -260476, 27361681, 9257833, -1956526, -1776914 },
            { -25045300, -10191966, 15366585, 15166509, -13105086, 8423556, -29171540, 12361135, -18685978, 4578290 },
        },
        {
            { 24579768, 3711570, 1342322, -11180126, -27005135, 14124956, -22544529, 14074919, 21964432, 8235257 },
            { -6528613, -2411497, 9442966, -5925588, 12025640, -1487420, -2981514, -1669206, 13006806, 2355433 },
            { -16304899, -13605259, -6632427, -5142349, 16974359, -10911083, 27202044, 1719366, 1141648, -12796236 },
        },
        {
            { -12863944, -13219986, -8318266, -11018091, -6810145, -4843894, 13475066, -3133972, 32674895, 13715045 },
            { 11423335, -5468059, 32344216, 8962751, 24989809, 9241752, -13265253, 16086212, -28740881, -15642093 },
            { -1409668, 12530728, -6368726, 10847387, 19531186, -14132160, -11709148, 7791794, -27245943, 4383347 },
        },
    },
    {
        {
            { -28970898, 5271447, -1266009, -9736989, -12455236, 16732599, -4862407, -4906449, 27193557, 6245191 },
            { -15193956, 5362278, -1783893, 2695834, 4960227, 12840725, 23061898, 3260492, 22510453, 8577507 },
            { -12632451, 11257346, -32692994, 13548177, -721004, 10879011, 31168030, 13952092, -29571492, -3635906 },
        },
        {
            { 3877321, -9572739, 32416692, 5405324, -11004407, -13656635, 3759769, 11935320, 5611860, 8164018 },
            { -16275802, 14667797, 15906460, 12155291, -22111149, -9039718, 32003002, -8832289, 5773085, -8422109 },
            { -23788118, -8254300, 1950875, 8937633, 18686727, 16459170, -905725, 12376320, 31632953, 190926 },
        },
        {
            { -24593607, -16138885, -8423991, 13378746, 14162407, 6901328, -8288749, 4508564, -25341555, -3627528 },
            { 8884438, -5884009, 6023974, 10104341, -6881569, -4941533, 18722941, -14786005, -1672488, 827625 },
            { -32720583, -16289296, -32503547, 7101210, 13354605, 2659080, -1800575, -14108036, -24878478, 1541286 },
        },
        {
            { 2901347, -1117687, 3880376, -10059388, -17620940, -3612781, -21802117, -3567481, 20456845, -1885033 },
            { 27019610, 12299467, -13658288, -1603234, -12861660, -4861471, -19540150, -5016058, 29439641, 15138866 },
            { 21536104, -6626420, -32447818, -10690208, -22408077, 5175814, -5420040, -16361163, 7779328, 109896 },
        },
        {
            { 30279744, 14648750, -8044871, 6425558, 13639621, -743509, 28698390, 12180118, 23177719, -554075 },
            { 26572847, 3405927, -31701700, 12890905, -19265668, 5335866, -6493768, 2378492, 4439158, -13279347 },
            { -22716706, 3489070, -9225266, -332753, 18875722, -1140095, 14819434, -12731527, -17717757, -5461437 },
        },
        {
            { -5056483, 16566551, 15953661, 3767752, -10436499, 15627060, -820954, 2177225, 8550082, -15114165 },
            { -18473302, 16596775, -381660, 15663611, 22860960, 15585581, -27844109, -3582739, -23260460, -8428588 },
            { -32480551, 15707275, -8205912, -5652081, 29464558, 2713815, -22725137, 15860482, -21902570, 1494193 },
        },
        {
            { -19562091, -14087393, -25583872, -9299552, 13127842, 759709, 21923482, 16529112, 8742704, 12967017 },
            { -28464899, 1553205, 32536856, -10473729, -24691605, -406174, -8914625, -2933896, -29903758, 15553883 },
            { 21877909, 3230008, 9881174, 10539357, -4797115, 2841332, 11543572, 14513274, 19375923, -12647961 },
        },
        {
            { 8832269, -14495485, 13253511, 5137575, 5037871, 4078777, 24880818, -6222716, 2862653, 9455043 },
            { 29306751, 5123106, 20245049, -14149889, 9592566, 8447059, -2077124, -2990080, 15511449, 4789663 },
            { -20679756, 7004547, 8824831, -9434977, -4045704, -3750736, -5754762, 108893, 23513200, 16652362 },
        },
    },
    {
        {
            { -33256173, 4144782, -4476029, -6579123, 10770039, -7155542, -6650416, -12936300, -18319198, 10212860 },
            { 2756081, 8598110, 7383731, -6859892, 22312759, -1105012, 21179801, 2600940, -9988298, -12506466 },
            { -24645692, 13317462, -30449259, -15653928, 21365574, -10869657, 11344424, 864440, -2499677, -16710063 },
        },
        {
            { -26432803, 6148329, -17184412, -14474154, 18782929, -275997, -22561534, 211300, 2719757, 4940997 },
            { -1323882, 3911313, -6948744, 14759765, -30027150, 7851207, 21690126, 8518463, 26699843, 5276295 },
            { -13149873, -6429067, 9396249, 365013, 24703301, -10488939, 1321586, 149635, -15452774, 7159369 },
        },
        {
            { 9987780, -3404759, 17507962, 9505530, 9731535, -2165514, 22356009, 8312176, 22477218, -8403385 },
            { 18155857, -16504990, 19744716, 9006923, 15154154, -10538976, 24256460, -4864995, -22548173, 9334109 },
            { 2986088, -4911893, 10776628, -3473844, 10620590, -7083203, -21413845, 14253545, -22587149, 536906 },
        },
        {
            { 4377756, 8115836, 24567078, 15495314, 11625074, 13064599, 7390551, 10589625, 10838060, -15420424 },
            { -19342404, 867880, 9277171, -3218459, -14431572, -1986443, 19295826, -15796950, 6378260, 699185 },
            { 7895026, 4057113, -7081772, -13077756, -17886831, -323126, -716039, 15693155, -5045064, -13373962 },
        },
        {
            { -7737563, -5869402, -14566319, -7406919, 11385654, 13201616, 31730678, -10962840, -3918636, -9669325 },
            { 10188286, -15770834, -7336361, 13427543, 22223443, 14896287, 30743455, 7116568, -21786507, 5427593 },
            { 696102, 13206899, 27047647, -10632082, 15285305, -9853179, 10798490, -4578720, 19236243, 12477404 },
        },
        {
            { -11229439, 11243796, -17054270, -8040865, -788228, -8167967, -3897669, 11180504, -23169516, 7733644 },
            { 17800790, -14036179, -27000429, -11766671, 23887827, 3149671, 23466177, -10538171, 10322027, 15313801 },
            { 26246234, 11968874, 32263343, -5468728, 6830755, -13323031, -15794704, -101982, -24449242, 10890804 },
        },
        {
            { -31365647, 10271363, -12660625, -6267268, 16690207, -13062544, -14982212, 16484931, 25180797, -5334884 },
            { -586574, 10376444, -32586414, -11286356, 19801893, 10997610, 2276632, 9482883, 316878, 13820577 },
            { -9882808, -4510367, -2115506, 16457136, -11100081, 11674996, 30756178, -7515054, 30696930, -3712849 },
        },
        {
            { 32988917, -9603412, 12499366, 7910787, -10617257, -11931514, -7342816, -9985397, -32349517, 7392473 },
            { -8855661, 15927861, 9866406, -3649411, -2396914, -16655781, -30409476, -9134995, 25112947, -2926644 },
            { -2504044, -436966, 25621774, -5678772, 15085042, -5479877, -24884878, -13526194, 5537438, -13914319 },
        },
    },
    {
        {
            { -11225584, 2320285, -9584280, 10149187, -33444663, 5808648, -14876251, -1729667, 31234590, 6090599 },
            { -9633316, 116426, 26083934, 2897444, -6364437, -2688086, 609721, 15878753, -6970405, -9034768 },
            { -27757857, 247744, -15194774, -9002551, 23288161, -10011936, -23869595, 6503646, 20650474, 1804084 },
        },
        {
            { -27589786, 15456424, 8972517, 8469608, 15640622, 4439847, 3121995, -10329713, 27842616, -202328 },
            { -15306973, 2839644, 22530074, 10026331, 4602058, 5048462, 28248656, 5031932, -11375082, 12714369 },
            { 20807691, -7270825, 29286141, 11421711, -27876523, -13868230, -21227475, 1035546, -19733229, 12796920 },
        },
        {
            { 12076899, -14301286, -8785001, -11848922, -25012791, 16400684, -17591495, -12899438, 3480665, -15182815 },
            { -32361549, 5457597, 28548107, 7833186, 7303070, -11953545, -24363064, -15921875, -33374054, 2771025 },
            { -21389266, 421932, 26597266, 6860826, 22486084, -6737172, -17137485, -4210226, -24552282, 15673397 },
        },
        {
            { -20184622, 2338216, 19788685, -9620956, -4001265, -8740893, -20271184, 4733254, 3727144, -12934448 },
            { 6120119, 814863, -11794402, -622716, 6812205, -15747771, 2019594, 7975683, 31123697, -10958981 },
            { 30069250, -11435332, 30434654, 2958439, 18399564, -976289, 12296869, 9204260, -16432438, 9648165 },
        },
        {
            { 32705432, -1550977, 30705658, 7451065, -11805606, 9631813, 3305266, 5248604, -26008332, -11377501 },
            { 17219865, 2375039, -31570947, -5575615, -19459679, 9219903, 294711, 15298639, 2662509, -16297073 },
            { -1172927, -7558695, -4366770, -4287744, -21346413, -8434326, 32087529, -1222777, 32247248, -14389861 },
        },
        {
            { 14312628, 1221556, 17395390, -8700143, -4945741, -8684635, -28197744, -9637817, -16027623, -13378845 },
            { -1428825, -9678990, -9235681, 6549687, -7383069, -468664, 23046502, 9803137, 17597934, 2346211 },
            { 18510800, 15337574, 26171504, 981392, -22241552, 7827556, -23491134, -11323352, 3059833, -11782870 },
        },
        {
            { 10141598, 6082907, 17829293, -1947643, 9830092, 13613136, -25556636, -5544586, -33502212, 3592096 },
            { 33114168, -15889352, -26525686, -13343397, 33076705, 8716171, 1151462, 1521897, -982665, -6837803 },
            { -32939165, -4255815, 23947181, -324178, -33072974, -12305637, -16637686, 3891704, 26353178, 693168 },
        },
        {
            { 30374239, 1595580, -16884039, 13186931, 4600344, 406904, 9585294, -400668, 31375464, 14369965 },
            { -14370654, -7772529, 1510301, 6434173, -18784789, -6262728, 32732230, -13108839, 17901441, 16011505 },
            { 18171223, -11934626, -12500402, 15197122, -11038147, -15230035, -19172240, -16046376, 8764035, 12309598 },
        },
    },
    {
        {
            { 5975908, -5243188, -19459362, -9681747, -11541277, 14015782, -23665757, 1228319, 17544096, -10593782 },
            { 5811932, -1715293, 3442887, -2269310, -18367348, -8359541, -18044043, -15410127, -5565381, 12348900 },
            { -31399660, 11407555, 25755363, 6891399, -3256938, 14872274, -24849353, 8141295, -10632534, -585479 },
        },
        {
            { -12675304, 694026, -5076145, 13300344, 14015258, -14451394, -9698672, -11329050, 30944593, 1130208 },
            { 8247766, -6710942, -26562381, -7709309, -14401939, -14648910, 4652152, 2488540, 23550156, -271232 },
            { 17294316, -3788438, 7026748, 15626851, 22990044, 113481, 2267737, -5908146, -408818, -137719 },
        },
        {
            { 16091085, -16253926, 18599252, 7340678, 2137637, -1221657, -3364161, 14550936, 3260525, -7166271 },
            { -4910104, -13332887, 18550887, 10864893, -16459325, -7291596, -23028869, -13204905, -12748722, 2701326 },
            { -8574695, 16099415, 4629974, -16340524, -20786213, -6005432, -10018363, 9276971, 11329923, 1862132 },
        },
        {
            { 14763076, -15903608, -30918270, 3689867, 3511892, 10313526, -21951088, 12219231, -9037963, -940300 },
            { 8894987, -3446094, 6150753, 3013931, 301220, 15693451, -31981216, -2909717, -15438168, 11595570 },
            { 15214962, 3537601, -26238722, -14058872, 4418657, -15230761, 13947276, 10730794, -13489462, -4363670 },
        },
        {
            { -2538306, 7682793, 32759013, 263109, -29984731, -7955452, -22332124, -10188635, 977108, 699994 },
            { -12466472, 4195084, -9211532, 550904, -15565337, 12917920, 19118110, -439841, -30534533, -14337913 },
            { 31788461, -14507657, 4799989, 7372237, 8808585, -14747943, 9408237, -10051775, 12493932, -5409317 },
        },
        {
            { -25680606, 5260744, -19235809, -6284470, -3695942, 16566087, 27218280, 2607121, 29375955, 6024730 },
            { 842132, -2794693, -4763381, -8722815, 26332018, -12405641, 11831880, 6985184, -9940361, 2854096 },
            { -4847262, -7969331, 2516242, -5847713, 9695691, -7221186, 16512645, 960770, 12121869, 16648078 },
        },
        {
            { -15218652, 14667096, -13336229, 2013717, 30598287, -464137, -31504922, -7882064, 20237806, 2838411 },
            { -19288047, 4453152, 15298546, -16178388, 22115043, -15972604, 12544294, -13470457, 1068881, -12499905 },
            { -9558883, -16518835, 33238498, 13506958, 30505848, -1114596, -8486907, -2630053, 12521378, 4845654 },
        },
        {
            { -28198521, 10744108, -2958380, 10199664, 7759311, -13088600, 3409348, -873400, -6482306, -12885870 },
            { -23561822, 6230156, -20382013, 10655314, -24040585, -11621172, 10477734, -1240216, -3113227, 13974498 },
            { 12966261, 15550616, -32038948, -1615346, 21025980, -629444, 5642325, 7188737, 18895762, 12629579 },
        },
    },
    {
        {
            { 14741879, -14946887, 22177208, -11721237, 1279741, 8058600, 11758140, 789443, 32195181, 3895677 },
            { 10758205, 15755439, -4509950, 9243698, -4879422, 6879879, -2204575, -3566119, -8982069, 4429647 },
            { -2453894, 15725973, -20436342, -10410672, -5803908, -11040220, -7135870, -11642895, 18047436, -15281743 },
        },
        {
            { -25173001, -11307165, 29759956, 11776784, -22262383, -15820455, 10993114, -12850837, -17620701, -9408468 },
            { 21987233, 700364, -24505048, 14972008, -7774265, -5718395, 32155026, 2581431, -29958985, 8773375 },
            { -25568350, 454463, -13211935, 16126715, 25240068, 8594567, 20656846, 12017935, -7874389, -13920155 },
        },
        {
            { 6028182, 6263078, -31011806, -11301710, -818919, 2461772, -31841174, -5468042, -1721788, -2776725 },
            { -12278994, 16624277, 987579, -5922598, 32908203, 1248608, 7719845, -4166698, 28408820, 6816612 },
            { -10358094, -8237829, 19549651, -12169222, 22082623, 16147817, 20613181, 13982702, -10339570, 5067943 },
        },
        {
            { -30505967, -3821767, 12074681, 13582412, -19877972, 2443951, -19719286, 12746132, 5331210, -10105944 },
            { 30528811, 3601899, -1957090, 4619785, -27361822, -15436388, 24180793, -12570394, 27679908, -1648928 },
            { 9402404, -13957065, 32834043, 10838634, -26580150, -13237195, 26653274, -8685565, 22611444, -12715406 },
        },
        {
            { 22190590, 1118029, 22736441, 15130463, -30460692, -5991321, 19189625, -4648942, 4854859, 6622139 },
            { -8310738, -2953450, -8262579, -3388049, -10401731, -271929, 13424426, -3567227, 26404409, 13001963 },
            { -31241838, -15415700, -2994250, 8939346, 11562230, -12840670, -26064365, -11621720, -15405155, 11020693 },
        },
        {
            { 1866042, -7949489, -7898649, -10301010, 12483315, 13477547, 3175636, -12424163, 28761762, 1406734 },
            { -448555, -1777666, 13018551, 3194501, -9580420, -11161737, 24760585, -4347088, 25577411, -13378680 },
            { -24290378, 4759345, -690653, -1852816, 2066747, 10693769, -29595790, 9884936, -9368926, 4745410 },
        },
        {
            { -9141284, 6049714, -19531061, -4341411, -31260798, 9944276, -15462008, -11311852, 10931924, -11931931 },
            { -16561513, 14112680, -8012645, 4817318, -8040464, -11414606, -22853429, 10856641, -20470770, 13434654 },
            { 22759489, -10073434, -16766264, -1871422, 13637442, -10168091, 1765144, -12654326, 28445307, -5364710 },
        },
        {
            { 29875063, 12493613, 2795536, -3786330, 1710620, 15181182, -10195717, -8788675, 9074234, 1167180 },
            { -26205683, 11014233, -9842651, -2635485, -26908120, 7532294, -18716888, -9535498, 3843903, 9367684 },
            { -10969595, -6403711, 9591134, 9582310, 11349256, 108879, 16235123, 8601684, -139197, 4242895 },
        },
    },
    {
        {
            { 22092954, -13191123, -2042793, -11968512, 32186753, -11517388, -6574341, 2470660, -27417366, 16625501 },
            { -11057722, 3042016, 13770083, -9257922, 584236, -544855, -7770857, 2602725, -27351616, 14247413 },
            { 6314175, -10264892, -32772502, 15957557, -10157730, 168750, -8618807, 14290061, 27108877, -1180880 },
        },
        {
            { -8586597, -7170966, 13241782, 10960156, -32991015, -13794596, 33547976, -11058889, -27148451, 981874 },
            { 22833440, 9293594, -32649448, -13618667, -9136966, 14756819, -22928859, -13970780, -10479804, -16197962 },
            { -7768587, 3326786, -28111797, 10783824, 19178761, 14905060, 22680049, 13906969, -15933690, 3797899 },
        },
        {
            { 21721356, -4212746, -12206123, 9310182, -3882239, -13653110, 23740224, -2709232, 20491983, -8042152 },
            { 9209270, -15135055, -13256557, -6167798, -731016, 15289673, 25947805, 15286587, 30997318, -6703063 },
            { 7392032, 16618386, 23946583, -8039892, -13265164, -1533858, -14197445, -2321576, 17649998, -250080 },
        },
        {
            { -9301088, -14193827, 30609526, -3049543, -25175069, -1283752, -15241566, -9525724, -2233253, 7662146 },
            { -17558673, 1763594, -33114336, 15908610, -30040870, -12174295, 7335080, -8472199, -3174674, 3440183 },
            { -19889700, -5977008, -24111293, -9688870, 10799743, -16571957, 40450, -4431835, 4862400, 1133 },
        },
        {
            { -32856209, -7873957, -5422389, 14860950, -16319031, 7956142, 7258061, 311861, -30594991, -7379421 },
            { -3773428, -1565936, 28985340, 7499440, 24445838, 9325937, 29727763, 16527196, 18278453, 15405622 },
            { -4381906, 8508652, -19898366, -3674424, -5984453, 15149970, -13313598, 843523, -21875062, 13626197 },
        },
        {
            { 2281448, -13487055, -10915418, -2609910, 1879358, 16164207, -10783882, 3953792, 13340839, 15928663 },
            { 31727126, -7179855, -18437503, -8283652, 2875793, -16390330, -25269894, -7014826, -23452306, 5964753 },
            { 4100420, -5959452, -17179337, 6017714, -18705837, 12227141, -26684835, 11344144, 2538215, -7570755 },
        },
        {
            { -9433605, 6123113, 11159803, -2156608, 30016280, 14966241, -20474983, 1485421, -629256, -15958862 },
            { -26804558, 4260919, 11851389, 9658551, -32017107, 16367492, -20205425, -13191288, 11659922, -11115118 },
            { 26180396, 10015009, -30844224, -8581293, 5418197, 9480663, 2231568, -10170080, 33100372, -1306171 },
        },
        {
            { 15121113, -5201871, -10389905, 15427821, -27509937, -15992507, 21670947, 4486675, -5931810, -14466380 },
            { 16166486, -9483733, -11104130, 6023908, -31926798, -1364923, 2340060, -16254968, -10735770, -10039824 },
            { 28042865, -3557089, -12126526, 12259706, -3717498, -6945899, 6766453, -8689599, 18036436, 5803270 },
        },
    },
    {
        {
            { -817581, 6763912, 11803561, 1585585, 10958447, -2671165, 23855391, 4598332, -6159431, -14117438 },
            { -31031306, -14256194, 17332029, -2383520, 31312682, -5967183, 696309, 50292, -20095739, 11763584 },
            { -594563, -2514283, -32234153, 12643980, 12650761, 14811489, 665117, -12613632, -19773211, -10713562 },
        },
        {
            { 30464590, -11262872, -4127476, -12734478, 19835327, -7105613, -24396175, 2075773, -17020157, 992471 },
            { 18357185, -6994433, 7766382, 16342475, -29324918, 411174, 14578841, 8080033, -11574335, -10601610 },
            { 19598397, 10334610, 12555054, 2555664, 18821899, -10339780, 21873263, 16014234, 26224780, 16452269 },
        },
        {
            { -30223925, 5145196, 5944548, 16385966, 3976735, 2009897, -11377804, -7618186, -20533829, 3698650 },
            { 14187449, 3448569, -10636236, -10810935, -22663880, -3433596, 7268410, -10890444, 27394301, 12015369 },
            { 19695761, 16087646, 28032085, 12999827, 6817792, 11427614, 20244189, -1312777, -13259127, -3402461 },
        },
        {
            { 30860103, 12735208, -1888245, -4699734, -16974906, 2256940, -8166013, 12298312, -8550524, -10393462 },
            { -5719826, -11245325, -1910649, 15569035, 26642876, -7587760, -5789354, -15118654, -4976164, 12651793 },
            { -2848395, 9953421, 11531313, -5282879, 26895123, -12697089, -13118820, -16517902, 9768698, -2533218 },
        },
        {
            { -24719459, 1894651, -287698, -4704085, 15348719, -8156530, 32767513, 12765450, 4940095, 10678226 },
            { 18860224, 15980149, -18987240, -1562570, -26233012, -11071856, -7843882, 13944024, -24372348, 16582019 },
            { -15504260, 4970268, -29893044, 4175593, -20993212, -2199756, -11704054, 15444560, -11003761, 7989037 },
        },
        {
            { 31490452, 5568061, -2412803, 2182383, -32336847, 4531686, -32078269, 6200206, -19686113, -14800171 },
            { -17308668, -15879940, -31522777, -2831, -32887382, 16375549, 8680158, -16371713, 28550068, -6857132 },
            { -28126887, -5688091, 16837845, -1820458, -6850681, 12700016, -30039981, 4364038, 1155602, 5988841 },
        },
        {
            { 21890435, -13272907, -12624011, 12154349, -7831873, 15300496, 23148983, -4470481, 24618407, 8283181 },
            { -33136107, -10512751, 9975416, 6841041, -31559793, 16356536, 3070187, -7025928, 1466169, 10740210 },
            { -1509399, -15488185, -13503385, -10655916, 32799044, 909394, -13938903, -5779719, -32164649, -15327040 },
        },
        {
            { 3960823, -14267803, -28026090, -15918051, -19404858, 13146868, 15567327, 951507, -3260321, -573935 },
            { 24740841, 5052253, -30094131, 8961361, 25877428, 6165135, -24368180, 14397372, -7380369, -6144105 },
            { -28888365, 3510803, -28103278, -1158478, -11238128, -10631454, -15441463, -14453128, -1625486, -6494814 },
        },
    },
    {
        {
            { 793299, -9230478, 8836302, -6235707, -27360908, -2369593, 33152843, -4885251, -9906200, -621852 },
            { 5666233, 525582, 20782575, -8038419, -24538499, 14657740, 16099374, 1468826, -6171428, -15186581 },
            { -4859255, -3779343, -2917758, -6748019, 7778750, 11688288, -30404353, -9871238, -1558923, -9863646 },
        },
        {
            { 10896332, -7719704, 824275, 472601, -19460308, 3009587, 25248958, 14783338, -30581476, -15757844 },
            { 10566929, 12612572, -31944212, 11118703, -12633376, 12362879, 21752402, 8822496, 24003793, 14264025 },
            { 27713862, -7355973, -11008240, 9227530, 27050101, 2504721, 23886875, -13117525, 13958495, -5732453 },
        },
        {
            { -23481610, 4867226, -27247128, 3900521, 29838369, -8212291, -31889399, -10041781, 7340521, -15410068 },
            { 4646514, -8011124, -22766023, -11532654, 23184553, 8566613, 31366726, -1381061, -15066784, -10375192 },
            { -17270517, 12723032, -16993061, 14878794, 21619651, -6197576, 27584817, 3093888, -8843694, 3849921 },
        },
        {
            { -9064912, 2103172, 25561640, -15125738, -5239824, 9582958, 32477045, -9017955, 5002294, -15550259 },
            { -12057553, -11177906, 21115585, -13365155, 8808712, -12030708, 16489530, 13378448, -25845716, 12741426 },
            { -5946367, 10645103, -30911586, 15390284, -3286982, -7118677, 24306472, 15852464, 28834118, -7646072 },
        },
        {
            { -17335748, -9107057, -24531279, 9434953, -8472084, -583362, -13090771, 455841, 20461858, 5491305 },
            { 13669248, -16095482, -12481974, -10203039, -14569770, -11893198, -24995986, 11293807, -28588204, -9421832 },
            { 28497928, 6272777, -33022994, 14470570, 8906179, -1225630, 18504674, -14165166, 29867745, -8795943 },
        },
        {
            { -16207023, 13517196, -27799630, -13697798, 24009064, -6373891, -6367600, -13175392, 22853429, -4012011 },
            { 24191378, 16712145, -13931797, 15217831, 14542237, 1646131, 18603514, -11037887, 12876623, -2112447 },
            { 17902668, 4518229, -411702, -2829247, 26878217, 5258055, -12860753, 608397, 16031844, 3723494 },
        },
        {
            { -28632773, 12763728, -20446446, 7577504, 33001348, -13017745, 17558842, -7872890, 23896954, -4314245 },
            { -20005381, -12011952, 31520464, 605201, 2543521, 5991821, -2945064, 7229064, -9919646, -8826859 },
            { 28816045, 298879, -28165016, -15920938, 19000928, -1665890, -12680833, -2949325, -18051778, -2082915 },
        },
        {
            { 16000882, -344896, 3493092, -11447198, -29504595, -13159789, 12577740, 16041268, -19715240, 7847707 },
            { 10151868, 10572098, 27312476, 7922682, 14825339, 4723128, -32855931, -6519018, -10020567, 3852848 },
            { -11430470, 15697596, -21121557, -4420647, 5386314, 15063598, 16514493, -15932110, 29330899, -15076224 },
        },
    },
    {
        {
            { -25499735, -4378794, -15222908, -6901211, 16615731, 2051784, 3303702, 15490, -27548796, 12314391 },
            { 15683520, -6003043, 18109120, -9980648, 15337968, -5997823, -16717435, 15921866, 16103996, -3731215 },
            { -23169824, -10781249, 13588192, -1628807, -3798557, -1074929, -19273607, 5402699, -29815713, -9841101 },
        },
        {
            { 23190676, 2384583, -32714340, 3462154, -29903655, -1529132, -11266856, 8911517, -25205859, 2739713 },
            { 21374101, -3554250, -33524649, 9874411, 15377179, 11831242, -33529904, 6134907, 4931255, 11987849 },
            { -7732, -2978858, -16223486, 7277597, 105524, -322051, -31480539, 13861388, -30076310, 10117930 },
        },
        {
            { -29501170, -10744872, -26163768, 13051539, -25625564, 5089643, -6325503, 6704079, 12890019, 15728940 },
            { -21972360, -11771379, -951059, -4418840, 14704840, 2695116, 903376, -10428139, 12885167, 8311031 },
            { -17516482, 5352194, 10384213, -13811658, 7506451, 13453191, 26423267, 4384730, 1888765, -5435404 },
        },
        {
            { -25817338, -3107312, -13494599, -3182506, 30896459, -13921729, -32251644, -12707869, -19464434, -3340243 },
            { -23607977, -2665774, -526091, 4651136, 5765089, 4618330, 6092245, 14845197, 17151279, -9854116 },
            { -24830458, -12733720, -15165978, 10367250, -29530908, -265356, 22825805, -7087279, -16866484, 16176525 },
        },
        {
            { -23583256, 6564961, 20063689, 3798228, -4740178, 7359225, 2006182, -10363426, -28746253, -10197509 },
            { -10626600, -4486402, -13320562, -5125317, 3432136, -6393229, 23632037, -1940610, 32808310, 1099883 },
            { 15030977, 5768825, -27451236, -2887299, -6427378, -15361371, -15277896, -6809350, 2051441, -15225865 },
        },
        {
            { -3362323, -7239372, 7517890, 9824992, 23555850, 295369, 5148398, -14154188, -22686354, 16633660 },
            { 4577086, -16752288, 13249841, -15304328, 19958763, -14537274, 18559670, -10759549, 8402478, -9864273 },
            { -28406330, -1051581, -26790155, -907698, -17212414, -11030789, 9453451, -14980072, 17983010, 9967138 },
        },
        {
            { -25762494, 6524722, 26585488, 9969270, 24709298, 1220360, -1677990, 7806337, 17507396, 3651560 },
            { -10420457, -4118111, 14584639, 15971087, -15768321, 8861010, 26556809, -5574557, -18553322, -11357135 },
            { 2839101, 14284142, 4029895, 3472686, 14402957, 12689363, -26642121, 8459447, -5605463, -7621941 },
        },
        {
            { -4839289, -3535444, 9744961, 2871048, 25113978, 3187018, -25110813, -849066, 17258084, -7977739 },
            { 18164541, -10595176, -17154882, -1542417, 19237078, -9745295, 23357533, -15217008, 26908270, 12150756 },
            { -30264870, -7647865, 5112249, -7036672, -1499807, -6974257, 43168, -5537701, -32302074, 16215819 },
        },
    },
    {
        {
            { -6898905, 9824394, -12304779, -4401089, -31397141, -6276835, 32574489, 12532905, -7503072, -8675347 },
            { -27343522, -16515468, -27151524, -10722951, 946346, 16291093, 254968, 7168080, 21676107, -1943028 },
            { 21260961, -8424752, -16831886, -11920822, -23677961, 3968121, -3651949, -6215466, -3556191, -7913075 },
        },
        {
            { 16544754, 13250366, -16804428, 15546242, -4583003, 12757258, -2462308, -8680336, -18907032, -9662799 },
            { -2415239, -15577728, 18312303, 4964443, -15272530, -12653564, 26820651, 16690659, 25459437, -4564609 },
            { -25144690, 11425020, 28423002, -11020557, -6144921, -15826224, 9142795, -2391602, -6432418, -1644817 },
        },
        {
            { -23104652, 6253476, 16964147, -3768872, -25113972, -12296437, -27457225, -16344658, 6335692, 7249989 },
            { -30333227, 13979675, 7503222, -12368314, -11956721, -4621693, -30272269, 2682242, 25993170, -12478523 },
            { 4364628, 5930691, 32304656, -10044554, -8054781, 15091131, 22857016, -10598955, 31820368, 15075278 },
        },
        {
            { 31879134, -8918693, 17258761, 90626, -8041836, -4917709, 24162788, -9650886, -17970238, 12833045 },
            { 19073683, 14851414, -24403169, -11860168, 7625278, 11091125, -19619190, 2074449, -9413939, 14905377 },
            { 24483667, -11935567, -2518866, -11547418, -1553130, 15355506, -25282080, 9253129, 27628530, -7555480 },
        },
        {
            { 17597607, 8340603, 19355617, 552187, 26198470, -3176583, 4593324, -9157582, -14110875, 15297016 },
            { 510886, 14337390, -31785257, 16638632, 6328095, 2713355, -20217417, -11864220, 8683221, 2921426 },
            { 18606791, 11874196, 27155355, -5281482, -24031742, 6265446, -25178240, -1278924, 4674690, 13890525 },
        },
        {
            { 13609624, 13069022, -27372361, -13055908, 24360586, 9592974, 14977157, 9835105, 4389687, 288396 },
            { 9922506, -519394, 13613107, 5883594, -18758345, -434263, -12304062, 8317628, 23388070, 16052080 },
            { 12720016, 11937594, -31970060, -5028689, 26900120, 8561328, -20155687, -11632979, -14754271, -10812892 },
        },
        {
            { 15961858, 14150409, 26716931, -665832, -22794328, 13603569, 11829573, 7467844, -28822128, 929275 },
            { 11038231, -11582396, -27310482, -7316562, -10498527, -16307831, -23479533, -9371869, -21393143, 2465074 },
            { 20017163, -4323226, 27915242, 1529148, 12396362, 15675764, 13817261, -9658066, 2463391, -4622140 },
        },
        {
            { -16358878, -12663911, -12065183, 4996454, -1256422, 1073572, 9583558, 12851107, 4003896, 12673717 },
            { -1731589, -15155870, -3262930, 16143082, 19294135, 13385325, 14741514, -9103726, 7903886, 2348101 },
            { 24536016, -16515207, 12715592, -3862155, 1511293, 10047386, -3842346, -7129159, -28377538, 10048127 },
        },
    },
    {
        {
            { -12622226, -6204820, 30718825, 2591312, -10617028, 12192840, 18873298, -7297090, -32297756, 15221632 },
            { -26478122, -11103864, 11546244, -1852483, 9180880, 7656409, -21343950, 2095755, 29769758, 6593415 },
            { -31994208, -2907461, 4176912, 3264766, 12538965, -868111, 26312345, -6118678, 30958054, 8292160 },
        },
        {
            { 31429822, -13959116, 29173532, 15632448, 12174511, -2760094, 32808831, 3977186, 26143136, -3148876 },
            { 22648901, 1402143, -22799984, 13746059, 7936347, 365344, -8668633, -1674433, -3758243, -2304625 },
            { -15491917, 8012313, -2514730, -12702462, -23965846, -10254029, -1612713, -1535569, -16664475, 8194478 },
        },
        {
            { 27338066, -7507420, -7414224, 10140405, -19026427, -6589889, 27277191, 8855376, 28572286, 3005164 },
            { 26287124, 4821776, 25476601, -4145903, -3764513, -15788984, -18008582, 1182479, -26094821, -13079595 },
            { -7171154, 3178080, 23970071, 6201893, -17195577, -4489192, -21876275, -13982627, 32208683, -1198248 },
        },
        {
            { -16657702, 2817643, -10286362, 14811298, 6024667, 13349505, -27315504, -10497842, -27672585, -11539858 },
            { 15941029, -9405932, -21367050, 8062055, 31876073, -238629, -15278393, -1444429, 15397331, -4130193 },
            { 8934485, -13485467, -23286397, -13423241, -32446090, 14047986, 31170398, -1441021, -27505566, 15087184 },
        },
        {
            { -18357243, -2156491, 24524913, -16677868, 15520427, -6360776, -15502406, 11461896, 16788528, -5868942 },
            { -1947386, 16013773, 21750665, 3714552, -17401782, -16055433, -3770287, -10323320, 31322514, -11615635 },
            { 21426655, -5650218, -13648287, -5347537, -28812189, -4920970, -18275391, -14621414, 13040862, -12112948 },
        },
        {
            { 11293895, 12478086, -27136401, 15083750, -29307421, 14748872, 14555558, -13417103, 1613711, 4896935 },
            { -25894883, 15323294, -8489791, -8057900, 25967126, -13425460, 2825960, -4897045, -23971776, -11267415 },
            { -15924766, -5229880, -17443532, 6410664, 3622847, 10243618, 20615400, 12405433, -23753030, -8436416 },
        },
        {
            { -7091295, 12556208, -20191352, 9025187, -17072479, 4333801, 4378436, 2432030, 23097949, -566018 },
            { 4565804, -16025654, 20084412, -7842817, 1724999, 189254, 24767264, 10103221, -18512313, 2424778 },
            { 366633, -11976806, 8173090, -6890119, 30788634, 5745705, -7168678, 1344109, -3642553, 12412659 },
        },
        {
            { -24001791, 7690286, 14929416, -168257, -32210835, -13412986, 24162697, -15326504, -3141501, 11179385 },
            { 18289522, -14724954, 8056945, 16430056, -21729724, 7842514, -6001441, -1486897, -18684645, -11443503 },
            { 476239, 6601091, -6152790, -9723375, 17503545, -4863900, 27672959, 13403813, 11052904, 5219329 },
        },
    },
    {
        {
            { 20678546, -8375738, -32671898, 8849123, -5009758, 14574752, 31186971, -3973730, 9014762, -8579056 },
            { -13644050, -10350239, -15962508, 5075808, -1514661, -11534600, -33102500, 9160280, 8473550, -3256838 },
            { 24900749, 14435722, 17209120, -15292541, -22592275, 9878983, -7689309, -16335821, -24568481, 11788948 },
        },
        {
            { -3118155, -11395194, -13802089, 14797441, 9652448, -6845904, -20037437, 10410733, -24568470, -1458691 },
            { -15659161, 16736706, -22467150, 10215878, -9097177, 7563911, 11871841, -12505194, -18513325, 8464118 },
            { -23400612, 8348507, -14585951, -861714, -3950205, -6373419, 14325289, 8628612, 33313881, -8370517 },
        },
        {
            { -20186973, -4967935, 22367356, 5271547, -1097117, -4788838, -24805667, -10236854, -8940735, -5818269 },
            { -6948785, -1795212, -32625683, -16021179, 32635414, -7374245, 15989197, -12838188, 28358192, -4253904 },
            { -23561781, -2799059, -32351682, -1661963, -9147719, 10429267, -16637684, 4072016, -5351664, 5596589 },
        },
        {
            { -28236598, -3390048, 12312896, 6213178, 3117142, 16078565, 29266239, 2557221, 1768301, 15373193 },
            { -7243358, -3246960, -4593467, -7553353, -127927, -912245, -1090902, -4504991, -24660491, 3442910 },
            { -30210571, 5124043, 14181784, 8197961, 18964734, -11939093, 22597931, 7176455, -18585478, 13365930 },
        },
        {
            { -7877390, -1499958, 8324673, 4690079, 6261860, 890446, 24538107, -8570186, -9689599, -3031667 },
            { 25008904, -10771599, -4305031, -9638010, 16265036, 15721635, 683793, -11823784, 15723479, -15163481 },
            { -9660625, 12374379, -27006999, -7026148, -7724114, -12314514, 11879682, 5400171, 519526, -1235876 },
        },
        {
            { 22258397, -16332233, -7869817, 14613016, -22520255, -2950923, -20353881, 7315967, 16648397, 7605640 },
            { -8081308, -8464597, -8223311, 9719710, 19259459, -15348212, 23994942, -5281555, -9468848, 4763278 },
            { -21699244, 9220969, -15730624, 1084137, -25476107, -2852390, 31088447, -7764523, -11356529, 728112 },
        },
        {
            { 26047220, -11751471, -6900323, -16521798, 24092068, 9158119, -4273545, -12555558, -29365436, -5498272 },
            { 17510331, -322857, 5854289, 8403524, 17133918, -3112612, -28111007, 12327945, 10750447, 10014012 },
            { -10312768, 3936952, 9156313, -8897683, 16498692, -994647, -27481051, -666732, 3424691, 7540221 },
        },
        {
            { 30322361, -6964110, 11361005, -4143317, 7433304, 4989748, -7071422, -16317219, -9244265, 15258046 },
            { 13054562, -2779497, 19155474, 469045, -12482797, 4566042, 5631406, 2711395, 1062915, -5136345 },
            { -19240248, -11254599, -29509029, -7499965, -5835763, 13005411, -6066489, 12194497, 32960380, 1459310 },
        },
    },
    {
        {
            { 19852034, 7027924, 23669353, 10020366, 8586503, -6657907, 394197, -6101885, 18638003, -11174937 },
            { 31395534, 15098109, 26581030, 8030562, -16527914, -5007134, 9012486, -7584354, -6643087, -5442636 },
            { -9192165, -2347377, -1997099, 4529534, 25766844, 607986, -13222, 9677543, -32294889, -6456008 },
        },
        {
            { -2444496, -149937, 29348902, 8186665, 1873760, 12489863, -30934579, -7839692, -7852844, -8138429 },
            { -15236356, -15433509, 7766470, 746860, 26346930, -10221762, -27333451, 10754588, -9431476, 5203576 },
            { 31834314, 14135496, -770007, 5159118, 20917671, -16768096, -7467973, -7337524, 31809243, 7347066 },
        },
        {
            { -9606723, -11874240, 20414459, 13033986, 13716524, -11691881, 19797970, -12211255, 15192876, -2087490 },
            { -12663563, -2181719, 1168162, -3804809, 26747877, -14138091, 10609330, 12694420, 33473243, -13382104 },
            { 33184999, 11180355, 15832085, -11385430, -1633671, 225884, 15089336, -11023903, -6135662, 14480053 },
        },
        {
            { 31308717, -5619998, 31030840, -1897099, 15674547, -6582883, 5496208, 13685227, 27595050, 8737275 },
            { -20318852, -15150239, 10933843, -16178022, 8335352, -7546022, -31008351, -12610604, 26498114, 66511 },
            { 22644454, -8761729, -16671776, 4884562, -3105614, -13559366, 30540766, -4286747, -13327787, -7515095 },
        },
        {
            { -28017847, 9834845, 18617207, -2681312, -3401956, -13307506, 8205540, 13585437, -17127465, 15115439 },
            { 23711543, -672915, 31206561, -8362711, 6164647, -9709987, -33535882, -1426096, 8236921, 16492939 },
            { -23910559, -13515526, -26299483, -4503841, 25005590, -7687270, 19574902, 10071562, 6708380, -6222424 },
        },
        {
            { 2101391, -4930054, 19702731, 2367575, -15427167, 1047675, 5301017, 9328700, 29955601, -11678310 },
            { 3096359, 9271816, -21620864, -15521844, -14847996, -7592937, -25892142, -12635595, -9917575, 6216608 },
            { -32615849, 338663, -25195611, 2510422, -29213566, -13820213, 24822830, -6146567, -26767480, 7525079 },
        },
        {
            { -23066649, -13985623, 16133487, -7896178, -3389565, 778788, -910336, -2782495, -19386633, 11994101 },
            { 21691500, -13624626, -641331, -14367021, 3285881, -3483596, -25064666, 9718258, -7477437, 13381418 },
            { 18445390, -4202236, 14979846, 11622458, -1727110, -3582980, 23111648, -6375247, 28535282, 15779576 },
        },
        {
            { 30098053, 3089662, -9234387, 16662135, -21306940, 11308411, -14068454, 12021730, 9955285, -16303356 },
            { 9734894, -14576830, -7473633, -9138735, 2060392, 11313496, -18426029, 9924399, 20194861, 13380996 },
            { -26378102, -7965207, -22167821, 15789297, -18055342, -6168792, -1984914, 15707771, 26342023, 10146099 },
        },
    },
    {
        {
            { -26016874, -219943, 21339191, -41388, 19745256, -2878700, -29637280, 2227040, 21612326, -545728 },
            { -13077387, 1184228, 23562814, -5970442, -20351244, -6348714, 25764461, 12243797, -20856566, 11649658 },
            { -10031494, 11262626, 27384172, 2271902, 26947504, -15997771, 39944, 6114064, 33514190, 2333242 },
        },
        {
            { -21433588, -12421821, 8119782, 7219913, -21830522, -9016134, -6679750, -12670638, 24350578, -13450001 },
            { -4116307, -11271533, -23886186, 4843615, -30088339, 690623, -31536088, -10406836, 8317860, 12352766 },
            { 18200138, -14475911, -33087759, -2696619, -23702521, -9102511, -23552096, -2287550, 20712163, 6719373 },
        },
        {
            { 26656208, 6075253, -7858556, 1886072, -28344043, 4262326, 11117530, -3763210, 26224235, -3297458 },
            { -17168938, -14854097, -3395676, -16369877, -19954045, 14050420, 21728352, 9493610, 18620611, -16428628 },
            { -13323321, 13325349, 11432106, 5964811, 18609221, 6062965, -5269471, -9725556, -30701573, -16479657 },
        },
        {
            { -23860538, -11233159, 26961357, 1640861, -32413112, -16737940, 12248509, -5240639, 13735342, 1934062 },
            { 25089769, 6742589, 17081145, -13406266, 21909293, -16067981, -15136294, -3765346, -21277997, 5473616 },
            { 31883677, -7961101, 1083432, -11572403, 22828471, 13290673, -7125085, 12469656, 29111212, -5451014 },
        },
        {
            { 24244947, -15050407, -26262976, 2791540, -14997599, 16666678, 24367466, 6388839, -10295587, 452383 },
            { -25640782, -3417841, 5217916, 16224624, 19987036, -4082269, -24236251, -5915248, 15766062, 8407814 },
            { -20406999, 13990231, 15495425, 16395525, 5377168, 15166495, -8917023, -4388953, -8067909, 2276718 },
        },
        {
            { 30157918, 12924066, -17712050, 9245753, 19895028, 3368142, -23827587, 5096219, 22740376, -7303417 },
            { 2041139, -14256350, 7783687, 13876377, -25946985, -13352459, 24051124, 13742383, -15637599, 13295222 },
            { 33338237, -8505733, 12532113, 7977527, 9106186, -1715251, -17720195, -4612972, -4451357, -14669444 },
        },
        {
            { -20045281, 5454097, -14346548, 6447146, 28862071, 1883651, -2469266, -4141880, 7770569, 9620597 },
            { 23208068, 7979712, 33071466, 8149229, 1758231, -10834995, 30945528, -1694323, -33502340, -14767970 },
            { 1439958, -16270480, -1079989, -793782, 4625402, 10647766, -5043801, 1220118, 30494170, -11440799 },
        },
        {
            { -5037580, -13028295, -2970559, -3061767, 15640974, -6701666, -26739026, 926050, -1684339, -13333647 },
            { 13908495, -3549272, 30919928, -6273825, -21521863, 7989039, 9021034, 9078865, 3353509, 4033511 },
            { -29663431, -15113610, 32259991, -344482, 24295849, -12912123, 23161163, 8839127, 27485041, 7356032 },
        },
    },
    {
        {
            { 9661027, 705443, 11980065, -5370154, -1628543, 14661173, -6346142, 2625015, 28431036, -16771834 },
            { -23839233, -8311415, -25945511, 7480958, -17681669, -8354183, -22545972, 14150565, 15970762, 4099461 },
            { 29262576, 16756590, 26350592, -8793563, 8529671, -11208050, 13617293, -9937143, 11465739, 8317062 },
        },
        {
            { -25493081, -6962928, 32500200, -9419051, -23038724, -2302222, 14898637, 3848455, 20969334, -5157516 },
            { -20384450, -14347713, -18336405, 13884722, -33039454, 2842114, -21610826, -3649888, 11177095, 14989547 },
            { -24496721, -11716016, 16959896, 2278463, 12066309, 10137771, 13515641, 2581286, -28487508, 9930240 },
        },
        {
            { -17751622, -2097826, 16544300, -13009300, -15914807, -14949081, 18345767, -13403753, 16291481, -5314038 },
            { -33229194, 2553288, 32678213, 9875984, 8534129, 6889387, -9676774, 6957617, 4368891, 9788741 },
            { 16660756, 7281060, -10830758, 12911820, 20108584, -8101676, -21722536, -8613148, 16250552, -11111103 },
        },
        {
            { -19765507, 2390526, -16551031, 14161980, 1905286, 6414907, 4689584, 10604807, -30190403, 4782747 },
            { -1354539, 14736941, -7367442, -13292886, 7710542, -14155590, -9981571, 4383045, 22546403, 437323 },
            { 31665577, -12180464, -16186830, 1491339, -18368625, 3294682, 27343084, 2786261, -30633590, -14097016 },
        },
        {
            { -14467279, -683715, -33374107, 7448552, 19294360, 14334329, -19690631, 2355319, -19284671, -6114373 },
            { 15121312, -15796162, 6377020, -6031361, -10798111, -12957845, 18952177, 15496498, -29380133, 11754228 },
            { -2637277, -13483075, 8488727, -14303896, 12728761, -1622493, 7141596, 11724556, 22761615, -10134141 },
        },
        {
            { 16918416, 11729663, -18083579, 3022987, -31015732, -13339659, -28741185, -12227393, 32851222, 11717399 },
            { 11166634, 7338049, -6722523, 4531520, -29468672, -7302055, 31474879, 3483633, -1193175, -4030831 },
            { -185635, 9921305, 31456609, -13536438, -12013818, 13348923, 33142652, 6546660, -19985279, -3948376 },
        },
        {
            { -32460596, 11266712, -11197107, -7899103, 31703694, 3855903, -8537131, -12833048, -30772034, -15486313 },
            { -18006477, 12709068, 3991746, -6479188, -21491523, -10550425, -31135347, -16049879, 10928917, 3011958 },
            { -6957757, -15594337, 31696059, 334240, 29576716, 14796075, -30831056, -12805180, 18008031, 10258577 },
        },
        {
            { -22448644, 15655569, 7018479, -4410003, -30314266, -1201591, -1853465, 1367120, 25127874, 6671743 },
            { 29701166, -14373934, -10878120, 9279288, -17568, 13127210, 21382910, 11042292, 25838796, 4642684 },
            { -20430234, 14955537, -24126347, 8124619, -5369288, -5990470, 30468147, -13900640, 18423289, 4177476 },
        },
    },
};



/*
r = p + q
*/

void ge_add(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) {
    fe t0;
    fe_add(r->X, p->Y, p->X);
    fe_sub(r->Y, p->Y, p->X);
    fe_mul(r->Z, r->X, q->YplusX);
    fe_mul(r->Y, r->Y, q->YminusX);
    fe_mul(r->T, q->T2d, p->T);
    fe_mul(r->X, p->Z, q->Z);
    fe_add(t0, r->X, r->X);
    fe_sub(r->X, r->Z, r->Y);
    fe_add(r->Y, r->Z, r->Y);
    fe_add(r->Z, t0, r->T);
    fe_sub(r->T, t0, r->T);
}


static void slide(signed char *r, const unsigned char *a) {
    int i;
    int b;
    int k;

    for (i = 0; i < 256; ++i) {
        r[i] = 1 & (a[i >> 3] >> (i & 7));
    }

    for (i = 0; i < 256; ++i)
        if (r[i]) {
            for (b = 1; b <= 6 && i + b < 256; ++b) {
                if (r[i + b]) {
                    if (r[i] + (r[i + b] << b) <= 15) {
                        r[i] += r[i + b] << b;
                        r[i + b] = 0;
                    } else if (r[i] - (r[i + b] << b) >= -15) {
                        r[i] -= r[i + b] << b;

                        for (k = i + b; k < 256; ++k) {
                            if (!r[k]) {
                                r[k] = 1;
                                break;
                            }

                            r[k] = 0;
                        }
                    } else {
                        break;
                    }
                }
            }
        }
}

/*
r = a * A + b * B
where a = a[0]+256*a[1]+...+256^31 a[31].
and b = b[0]+256*b[1]+...+256^31 b[31].
B is the Ed25519 base point (x,4/5) with x positive.
*/

void ge_double_scalarmult_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b) {
    signed char aslide[256];
    signed char bslide[256];
    ge_cached Ai[8]; /* A,3A,5A,7A,9A,11A,13A,15A */
    ge_p1p1 t;
    ge_p3 u;
    ge_p3 geA2;
    int i;
    slide(aslide, a);
    slide(bslide, b);
    ge_p3_to_cached(&Ai[0], A);
    ge_p3_dbl(&t, A);
    ge_p1p1_to_p3(&geA2, &t);
    ge_add(&t, &geA2, &Ai[0]);
    ge_p1p1_to_p3(&u, &t);
    ge_p3_to_cached(&Ai[1], &u);
    ge_add(&t, &geA2, &Ai[1]);
    ge_p1p1_to_p3(&u, &t);
    ge_p3_to_cached(&Ai[2], &u);
    ge_add(&t, &geA2, &Ai[2]);
    ge_p1p1_to_p3(&u, &t);
    ge_p3_to_cached(&Ai[3], &u);
    ge_add(&t, &geA2, &Ai[3]);
    ge_p1p1_to_p3(&u, &t);
    ge_p3_to_cached(&Ai[4], &u);
    ge_add(&t, &geA2, &Ai[4]);
    ge_p1p1_to_p3(&u, &t);
    ge_p3_to_cached(&Ai[5], &u);
    ge_add(&t, &geA2, &Ai[5]);
    ge_p1p1_to_p3(&u, &t);
    ge_p3_to_cached(&Ai[6], &u);
    ge_add(&t, &geA2, &Ai[6]);
    ge_p1p1_to_p3(&u, &t);
    ge_p3_to_cached(&Ai[7], &u);
    ge_p2_0(r);

    for (i = 255; i >= 0; --i) {
        if (aslide[i] || bslide[i]) {
            break;
        }
    }

    for (; i >= 0; --i) {
        ge_p2_dbl(&t, r);

        if (aslide[i] > 0) {
            ge_p1p1_to_p3(&u, &t);
            ge_add(&t, &u, &Ai[aslide[i] / 2]);
        } else if (aslide[i] < 0) {
            ge_p1p1_to_p3(&u, &t);
            ge_sub(&t, &u, &Ai[(-aslide[i]) / 2]);
        }

        if (bslide[i] > 0) {
            ge_p1p1_to_p3(&u, &t);
            ge_madd(&t, &u, &Bi[bslide[i] / 2]);
        } else if (bslide[i] < 0) {
            ge_p1p1_to_p3(&u, &t);
            ge_msub(&t, &u, &Bi[(-bslide[i]) / 2]);
        }

        ge_p1p1_to_p2(r, &t);
    }
}


static const fe d = {
    -10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116
};

static const fe sqrtm1 = {
    -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482
};

int ge_frombytes_negate_vartime(ge_p3 *h, const unsigned char *s) {
    fe u;
    fe v;
    fe v3;
    fe vxx;
    fe check;
    fe_frombytes(h->Y, s);
    fe_1(h->Z);
    fe_sq(u, h->Y);
    fe_mul(v, u, d);
    fe_sub(u, u, h->Z);     /* u = y^2-1 */
    fe_add(v, v, h->Z);     /* v = dy^2+1 */
    fe_sq(v3, v);
    fe_mul(v3, v3, v);      /* v3 = v^3 */
    fe_sq(h->X, v3);
    fe_mul(h->X, h->X, v);
    fe_mul(h->X, h->X, u);  /* x = uv^7 */
    fe_pow22523(h->X, h->X); /* x = (uv^7)^((q-5)/8) */
    fe_mul(h->X, h->X, v3);
    fe_mul(h->X, h->X, u);  /* x = uv^3(uv^7)^((q-5)/8) */
    fe_sq(vxx, h->X);
    fe_mul(vxx, vxx, v);
    fe_sub(check, vxx, u);  /* vx^2-u */

    if (fe_isnonzero(check)) {
        fe_add(check, vxx, u); /* vx^2+u */

        if (fe_isnonzero(check)) {
            return -1;
        }

        fe_mul(h->X, h->X, sqrtm1);
    }

    if (fe_isnegative(h->X) == (s[31] >> 7)) {
        fe_neg(h->X, h->X);
    }

    fe_mul(h->T, h->X, h->Y);
    return 0;
}


/*
r = p + q
*/

void ge_madd(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) {
    fe t0;
    fe_add(r->X, p->Y, p->X);
    fe_sub(r->Y, p->Y, p->X);
    fe_mul(r->Z, r->X, q->yplusx);
    fe_mul(r->Y, r->Y, q->yminusx);
    fe_mul(r->T, q->xy2d, p->T);
    fe_add(t0, p->Z, p->Z);
    fe_sub(r->X, r->Z, r->Y);
    fe_add(r->Y, r->Z, r->Y);
    fe_add(r->Z, t0, r->T);
    fe_sub(r->T, t0, r->T);
}


/*
r = p - q
*/

void ge_msub(ge_p1p1 *r, const ge_p3 *p, const ge_precomp *q) {
    fe t0;

    fe_add(r->X, p->Y, p->X);
    fe_sub(r->Y, p->Y, p->X);
    fe_mul(r->Z, r->X, q->yminusx);
    fe_mul(r->Y, r->Y, q->yplusx);
    fe_mul(r->T, q->xy2d, p->T);
    fe_add(t0, p->Z, p->Z);
    fe_sub(r->X, r->Z, r->Y);
    fe_add(r->Y, r->Z, r->Y);
    fe_sub(r->Z, t0, r->T);
    fe_add(r->T, t0, r->T);
}


/*
r = p
*/

void ge_p1p1_to_p2(ge_p2 *r, const ge_p1p1 *p) {
    fe_mul(r->X, p->X, p->T);
    fe_mul(r->Y, p->Y, p->Z);
    fe_mul(r->Z, p->Z, p->T);
}



/*
r = p
*/

void ge_p1p1_to_p3(ge_p3 *r, const ge_p1p1 *p) {
    fe_mul(r->X, p->X, p->T);
    fe_mul(r->Y, p->Y, p->Z);
    fe_mul(r->Z, p->Z, p->T);
    fe_mul(r->T, p->X, p->Y);
}


void ge_p2_0(ge_p2 *h) {
    fe_0(h->X);
    fe_1(h->Y);
    fe_1(h->Z);
}



/*
r = 2 * p
*/

void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p) {
    fe t0;

    fe_sq(r->X, p->X);
    fe_sq(r->Z, p->Y);
    fe_sq2(r->T, p->Z);
    fe_add(r->Y, p->X, p->Y);
    fe_sq(t0, r->Y);
    fe_add(r->Y, r->Z, r->X);
    fe_sub(r->Z, r->Z, r->X);
    fe_sub(r->X, t0, r->Y);
    fe_sub(r->T, r->T, r->Z);
}


void ge_p3_0(ge_p3 *h) {
    fe_0(h->X);
    fe_1(h->Y);
    fe_1(h->Z);
    fe_0(h->T);
}


/*
r = 2 * p
*/

void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p) {
    ge_p2 q;
    ge_p3_to_p2(&q, p);
    ge_p2_dbl(r, &q);
}



/*
r = p
*/

static const fe d2 = {
    -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199
};

void ge_p3_to_cached(ge_cached *r, const ge_p3 *p) {
    fe_add(r->YplusX, p->Y, p->X);
    fe_sub(r->YminusX, p->Y, p->X);
    fe_copy(r->Z, p->Z);
    fe_mul(r->T2d, p->T, d2);
}


/*
r = p
*/

void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p) {
    fe_copy(r->X, p->X);
    fe_copy(r->Y, p->Y);
    fe_copy(r->Z, p->Z);
}


void ge_p3_tobytes(unsigned char *s, const ge_p3 *h) {
    fe recip;
    fe x;
    fe y;
    fe_invert(recip, h->Z);
    fe_mul(x, h->X, recip);
    fe_mul(y, h->Y, recip);
    fe_tobytes(s, y);
    s[31] ^= fe_isnegative(x) << 7;
}


static unsigned char equal(signed char b, signed char c) {
    unsigned char ub = b;
    unsigned char uc = c;
    unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */
    uint64_t y = x; /* 0: yes; 1..255: no */
    y -= 1; /* large: yes; 0..254: no */
    y >>= 63; /* 1: yes; 0: no */
    return (unsigned char) y;
}

static unsigned char negative(signed char b) {
    uint64_t x = b; /* 18446744073709551361..18446744073709551615: yes; 0..255: no */
    x >>= 63; /* 1: yes; 0: no */
    return (unsigned char) x;
}

static void cmov(ge_precomp *t, const ge_precomp *u, unsigned char b) {
    fe_cmov(t->yplusx, u->yplusx, b);
    fe_cmov(t->yminusx, u->yminusx, b);
    fe_cmov(t->xy2d, u->xy2d, b);
}


static void ge_select(ge_precomp *t, int pos, signed char b) {
    ge_precomp minust;
    unsigned char bnegative = negative(b);
    unsigned char babs = b - (((-bnegative) & b) << 1);
    fe_1(t->yplusx);
    fe_1(t->yminusx);
    fe_0(t->xy2d);
    cmov(t, &base[pos][0], equal(babs, 1));
    cmov(t, &base[pos][1], equal(babs, 2));
    cmov(t, &base[pos][2], equal(babs, 3));
    cmov(t, &base[pos][3], equal(babs, 4));
    cmov(t, &base[pos][4], equal(babs, 5));
    cmov(t, &base[pos][5], equal(babs, 6));
    cmov(t, &base[pos][6], equal(babs, 7));
    cmov(t, &base[pos][7], equal(babs, 8));
    fe_copy(minust.yplusx, t->yminusx);
    fe_copy(minust.yminusx, t->yplusx);
    fe_neg(minust.xy2d, t->xy2d);
    cmov(t, &minust, bnegative);
}

/*
h = a * B
where a = a[0]+256*a[1]+...+256^31 a[31]
B is the Ed25519 base point (x,4/5) with x positive.

Preconditions:
  a[31] <= 127
*/

void ge_scalarmult_base(ge_p3 *h, const unsigned char *a) {
    signed char e[64];
    signed char carry;
    ge_p1p1 r;
    ge_p2 s;
    ge_precomp t;
    int i;

    for (i = 0; i < 32; ++i) {
        e[2 * i + 0] = (a[i] >> 0) & 15;
        e[2 * i + 1] = (a[i] >> 4) & 15;
    }

    /* each e[i] is between 0 and 15 */
    /* e[63] is between 0 and 7 */
    carry = 0;

    for (i = 0; i < 63; ++i) {
        e[i] += carry;
        carry = e[i] + 8;
        carry >>= 4;
        e[i] -= carry << 4;
    }

    e[63] += carry;
    /* each e[i] is between -8 and 8 */
    ge_p3_0(h);

    for (i = 1; i < 64; i += 2) {
        ge_select(&t, i / 2, e[i]);
        ge_madd(&r, h, &t);
        ge_p1p1_to_p3(h, &r);
    }

    ge_p3_dbl(&r, h);
    ge_p1p1_to_p2(&s, &r);
    ge_p2_dbl(&r, &s);
    ge_p1p1_to_p2(&s, &r);
    ge_p2_dbl(&r, &s);
    ge_p1p1_to_p2(&s, &r);
    ge_p2_dbl(&r, &s);
    ge_p1p1_to_p3(h, &r);

    for (i = 0; i < 64; i += 2) {
        ge_select(&t, i / 2, e[i]);
        ge_madd(&r, h, &t);
        ge_p1p1_to_p3(h, &r);
    }
}


/*
r = p - q
*/

void ge_sub(ge_p1p1 *r, const ge_p3 *p, const ge_cached *q) {
    fe t0;
    
    fe_add(r->X, p->Y, p->X);
    fe_sub(r->Y, p->Y, p->X);
    fe_mul(r->Z, r->X, q->YminusX);
    fe_mul(r->Y, r->Y, q->YplusX);
    fe_mul(r->T, q->T2d, p->T);
    fe_mul(r->X, p->Z, q->Z);
    fe_add(t0, r->X, r->X);
    fe_sub(r->X, r->Z, r->Y);
    fe_add(r->Y, r->Z, r->Y);
    fe_sub(r->Z, t0, r->T);
    fe_add(r->T, t0, r->T);
}


void ge_tobytes(unsigned char *s, const ge_p2 *h) {
    fe recip;
    fe x;
    fe y;
    fe_invert(recip, h->Z);
    fe_mul(x, h->X, recip);
    fe_mul(y, h->Y, recip);
    fe_tobytes(s, y);
    s[31] ^= fe_isnegative(x) << 7;
}

#endif // FOOP_GE


#define FOOP_FE
#ifdef FOOP_FE

//#include "fixedint.h"
//#include "src/fe.h"

/*
    helper functions
*/
static uint64_t load_3(const unsigned char *in) {
    uint64_t result;

    result = (uint64_t) in[0];
    result |= ((uint64_t) in[1]) << 8;
    result |= ((uint64_t) in[2]) << 16;

    return result;
}

static uint64_t load_4(const unsigned char *in) {
    uint64_t result;

    result = (uint64_t) in[0];
    result |= ((uint64_t) in[1]) << 8;
    result |= ((uint64_t) in[2]) << 16;
    result |= ((uint64_t) in[3]) << 24;
    
    return result;
}



/*
    h = 0
*/

void fe_0(fe h) {
    h[0] = 0;
    h[1] = 0;
    h[2] = 0;
    h[3] = 0;
    h[4] = 0;
    h[5] = 0;
    h[6] = 0;
    h[7] = 0;
    h[8] = 0;
    h[9] = 0;
}



/*
    h = 1
*/

void fe_1(fe h) {
    h[0] = 1;
    h[1] = 0;
    h[2] = 0;
    h[3] = 0;
    h[4] = 0;
    h[5] = 0;
    h[6] = 0;
    h[7] = 0;
    h[8] = 0;
    h[9] = 0;
}



/*
    h = f + g
    Can overlap h with f or g.

    Preconditions:
       |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
       |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.

    Postconditions:
       |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
*/

void fe_add(fe h, const fe f, const fe g) {
    int32_t f0 = f[0];
    int32_t f1 = f[1];
    int32_t f2 = f[2];
    int32_t f3 = f[3];
    int32_t f4 = f[4];
    int32_t f5 = f[5];
    int32_t f6 = f[6];
    int32_t f7 = f[7];
    int32_t f8 = f[8];
    int32_t f9 = f[9];
    int32_t g0 = g[0];
    int32_t g1 = g[1];
    int32_t g2 = g[2];
    int32_t g3 = g[3];
    int32_t g4 = g[4];
    int32_t g5 = g[5];
    int32_t g6 = g[6];
    int32_t g7 = g[7];
    int32_t g8 = g[8];
    int32_t g9 = g[9];
    int32_t h0 = f0 + g0;
    int32_t h1 = f1 + g1;
    int32_t h2 = f2 + g2;
    int32_t h3 = f3 + g3;
    int32_t h4 = f4 + g4;
    int32_t h5 = f5 + g5;
    int32_t h6 = f6 + g6;
    int32_t h7 = f7 + g7;
    int32_t h8 = f8 + g8;
    int32_t h9 = f9 + g9;
    
    h[0] = h0;
    h[1] = h1;
    h[2] = h2;
    h[3] = h3;
    h[4] = h4;
    h[5] = h5;
    h[6] = h6;
    h[7] = h7;
    h[8] = h8;
    h[9] = h9;
}



/*
    Replace (f,g) with (g,g) if b == 1;
    replace (f,g) with (f,g) if b == 0.

    Preconditions: b in {0,1}.
*/

void fe_cmov(fe f, const fe g, unsigned int b) {
    int32_t f0 = f[0];
    int32_t f1 = f[1];
    int32_t f2 = f[2];
    int32_t f3 = f[3];
    int32_t f4 = f[4];
    int32_t f5 = f[5];
    int32_t f6 = f[6];
    int32_t f7 = f[7];
    int32_t f8 = f[8];
    int32_t f9 = f[9];
    int32_t g0 = g[0];
    int32_t g1 = g[1];
    int32_t g2 = g[2];
    int32_t g3 = g[3];
    int32_t g4 = g[4];
    int32_t g5 = g[5];
    int32_t g6 = g[6];
    int32_t g7 = g[7];
    int32_t g8 = g[8];
    int32_t g9 = g[9];
    int32_t x0 = f0 ^ g0;
    int32_t x1 = f1 ^ g1;
    int32_t x2 = f2 ^ g2;
    int32_t x3 = f3 ^ g3;
    int32_t x4 = f4 ^ g4;
    int32_t x5 = f5 ^ g5;
    int32_t x6 = f6 ^ g6;
    int32_t x7 = f7 ^ g7;
    int32_t x8 = f8 ^ g8;
    int32_t x9 = f9 ^ g9;

    b = (unsigned int) (- (int) b); /* silence warning */
    x0 &= b;
    x1 &= b;
    x2 &= b;
    x3 &= b;
    x4 &= b;
    x5 &= b;
    x6 &= b;
    x7 &= b;
    x8 &= b;
    x9 &= b;
    
    f[0] = f0 ^ x0;
    f[1] = f1 ^ x1;
    f[2] = f2 ^ x2;
    f[3] = f3 ^ x3;
    f[4] = f4 ^ x4;
    f[5] = f5 ^ x5;
    f[6] = f6 ^ x6;
    f[7] = f7 ^ x7;
    f[8] = f8 ^ x8;
    f[9] = f9 ^ x9;
}

/*
    Replace (f,g) with (g,f) if b == 1;
    replace (f,g) with (f,g) if b == 0.

    Preconditions: b in {0,1}.
*/

void fe_cswap(fe f,fe g,unsigned int b) {
    int32_t f0 = f[0];
    int32_t f1 = f[1];
    int32_t f2 = f[2];
    int32_t f3 = f[3];
    int32_t f4 = f[4];
    int32_t f5 = f[5];
    int32_t f6 = f[6];
    int32_t f7 = f[7];
    int32_t f8 = f[8];
    int32_t f9 = f[9];
    int32_t g0 = g[0];
    int32_t g1 = g[1];
    int32_t g2 = g[2];
    int32_t g3 = g[3];
    int32_t g4 = g[4];
    int32_t g5 = g[5];
    int32_t g6 = g[6];
    int32_t g7 = g[7];
    int32_t g8 = g[8];
    int32_t g9 = g[9];
    int32_t x0 = f0 ^ g0;
    int32_t x1 = f1 ^ g1;
    int32_t x2 = f2 ^ g2;
    int32_t x3 = f3 ^ g3;
    int32_t x4 = f4 ^ g4;
    int32_t x5 = f5 ^ g5;
    int32_t x6 = f6 ^ g6;
    int32_t x7 = f7 ^ g7;
    int32_t x8 = f8 ^ g8;
    int32_t x9 = f9 ^ g9;
    b = (unsigned int) (- (int) b); /* silence warning */
    x0 &= b;
    x1 &= b;
    x2 &= b;
    x3 &= b;
    x4 &= b;
    x5 &= b;
    x6 &= b;
    x7 &= b;
    x8 &= b;
    x9 &= b;
    f[0] = f0 ^ x0;
    f[1] = f1 ^ x1;
    f[2] = f2 ^ x2;
    f[3] = f3 ^ x3;
    f[4] = f4 ^ x4;
    f[5] = f5 ^ x5;
    f[6] = f6 ^ x6;
    f[7] = f7 ^ x7;
    f[8] = f8 ^ x8;
    f[9] = f9 ^ x9;
    g[0] = g0 ^ x0;
    g[1] = g1 ^ x1;
    g[2] = g2 ^ x2;
    g[3] = g3 ^ x3;
    g[4] = g4 ^ x4;
    g[5] = g5 ^ x5;
    g[6] = g6 ^ x6;
    g[7] = g7 ^ x7;
    g[8] = g8 ^ x8;
    g[9] = g9 ^ x9;
}



/*
    h = f
*/

void fe_copy(fe h, const fe f) {
    int32_t f0 = f[0];
    int32_t f1 = f[1];
    int32_t f2 = f[2];
    int32_t f3 = f[3];
    int32_t f4 = f[4];
    int32_t f5 = f[5];
    int32_t f6 = f[6];
    int32_t f7 = f[7];
    int32_t f8 = f[8];
    int32_t f9 = f[9];
    
    h[0] = f0;
    h[1] = f1;
    h[2] = f2;
    h[3] = f3;
    h[4] = f4;
    h[5] = f5;
    h[6] = f6;
    h[7] = f7;
    h[8] = f8;
    h[9] = f9;
}



/*
    Ignores top bit of h.
*/

void fe_frombytes(fe h, const unsigned char *s) {
    int64_t h0 = load_4(s);
    int64_t h1 = load_3(s + 4) << 6;
    int64_t h2 = load_3(s + 7) << 5;
    int64_t h3 = load_3(s + 10) << 3;
    int64_t h4 = load_3(s + 13) << 2;
    int64_t h5 = load_4(s + 16);
    int64_t h6 = load_3(s + 20) << 7;
    int64_t h7 = load_3(s + 23) << 5;
    int64_t h8 = load_3(s + 26) << 4;
    int64_t h9 = (load_3(s + 29) & 8388607) << 2;
    int64_t carry0;
    int64_t carry1;
    int64_t carry2;
    int64_t carry3;
    int64_t carry4;
    int64_t carry5;
    int64_t carry6;
    int64_t carry7;
    int64_t carry8;
    int64_t carry9;

    carry9 = (h9 + (int64_t) (1 << 24)) >> 25;
    h0 += carry9 * 19;
    h9 -= carry9 << 25;
    carry1 = (h1 + (int64_t) (1 << 24)) >> 25;
    h2 += carry1;
    h1 -= carry1 << 25;
    carry3 = (h3 + (int64_t) (1 << 24)) >> 25;
    h4 += carry3;
    h3 -= carry3 << 25;
    carry5 = (h5 + (int64_t) (1 << 24)) >> 25;
    h6 += carry5;
    h5 -= carry5 << 25;
    carry7 = (h7 + (int64_t) (1 << 24)) >> 25;
    h8 += carry7;
    h7 -= carry7 << 25;
    carry0 = (h0 + (int64_t) (1 << 25)) >> 26;
    h1 += carry0;
    h0 -= carry0 << 26;
    carry2 = (h2 + (int64_t) (1 << 25)) >> 26;
    h3 += carry2;
    h2 -= carry2 << 26;
    carry4 = (h4 + (int64_t) (1 << 25)) >> 26;
    h5 += carry4;
    h4 -= carry4 << 26;
    carry6 = (h6 + (int64_t) (1 << 25)) >> 26;
    h7 += carry6;
    h6 -= carry6 << 26;
    carry8 = (h8 + (int64_t) (1 << 25)) >> 26;
    h9 += carry8;
    h8 -= carry8 << 26;

    h[0] = (int32_t) h0;
    h[1] = (int32_t) h1;
    h[2] = (int32_t) h2;
    h[3] = (int32_t) h3;
    h[4] = (int32_t) h4;
    h[5] = (int32_t) h5;
    h[6] = (int32_t) h6;
    h[7] = (int32_t) h7;
    h[8] = (int32_t) h8;
    h[9] = (int32_t) h9;
}



void fe_invert(fe out, const fe z) {
    fe t0;
    fe t1;
    fe t2;
    fe t3;
    int i;

    fe_sq(t0, z);

    // Odd code from original.  https://github.com/orlp/ed25519/blob/master/src/fe.c
    //for (i = 1; i < 1; ++i) {
    //    fe_sq(t0, t0);
    //}

    fe_sq(t1, t0);

    for (i = 1; i < 2; ++i) {
        fe_sq(t1, t1);
    }

    fe_mul(t1, z, t1);
    fe_mul(t0, t0, t1);
    fe_sq(t2, t0);

    //for (i = 1; i < 1; ++i) { // Odd
    //    fe_sq(t2, t2);
    //}

    fe_mul(t1, t1, t2);
    fe_sq(t2, t1);

    for (i = 1; i < 5; ++i) {
        fe_sq(t2, t2);
    }

    fe_mul(t1, t2, t1);
    fe_sq(t2, t1);

    for (i = 1; i < 10; ++i) {
        fe_sq(t2, t2);
    }

    fe_mul(t2, t2, t1);
    fe_sq(t3, t2);

    for (i = 1; i < 20; ++i) {
        fe_sq(t3, t3);
    }

    fe_mul(t2, t3, t2);
    fe_sq(t2, t2);

    for (i = 1; i < 10; ++i) {
        fe_sq(t2, t2);
    }

    fe_mul(t1, t2, t1);
    fe_sq(t2, t1);

    for (i = 1; i < 50; ++i) {
        fe_sq(t2, t2);
    }

    fe_mul(t2, t2, t1);
    fe_sq(t3, t2);

    for (i = 1; i < 100; ++i) {
        fe_sq(t3, t3);
    }

    fe_mul(t2, t3, t2);
    fe_sq(t2, t2);

    for (i = 1; i < 50; ++i) {
        fe_sq(t2, t2);
    }

    fe_mul(t1, t2, t1);
    fe_sq(t1, t1);

    for (i = 1; i < 5; ++i) {
        fe_sq(t1, t1);
    }

    fe_mul(out, t1, t0);
}



/*
    return 1 if f is in {1,3,5,...,q-2}
    return 0 if f is in {0,2,4,...,q-1}

    Preconditions:
       |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
*/

int fe_isnegative(const fe f) {
    unsigned char s[32];

    fe_tobytes(s, f);
    
    return s[0] & 1;
}



/*
    return 1 if f == 0
    return 0 if f != 0

    Preconditions:
       |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
*/

int fe_isnonzero(const fe f) {
    unsigned char s[32];
    unsigned char r;

    fe_tobytes(s, f);

    r = s[0];
    #define edF(i) r |= s[i]
    edF(1);
    edF(2);
    edF(3);
    edF(4);
    edF(5);
    edF(6);
    edF(7);
    edF(8);
    edF(9);
    edF(10);
    edF(11);
    edF(12);
    edF(13);
    edF(14);
    edF(15);
    edF(16);
    edF(17);
    edF(18);
    edF(19);
    edF(20);
    edF(21);
    edF(22);
    edF(23);
    edF(24);
    edF(25);
    edF(26);
    edF(27);
    edF(28);
    edF(29);
    edF(30);
    edF(31);
    #undef edF

    return r != 0;
}



/*
    h = f * g
    Can overlap h with f or g.

    Preconditions:
       |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.
       |g| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.

    Postconditions:
       |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
    */

    /*
    Notes on implementation strategy:

    Using schoolbook multiplication.
    Karatsuba would save a little in some cost models.

    Most multiplications by 2 and 19 are 32-bit precomputations;
    cheaper than 64-bit postcomputations.

    There is one remaining multiplication by 19 in the carry chain;
    one *19 precomputation can be merged into this,
    but the resulting data flow is considerably less clean.

    There are 12 carries below.
    10 of them are 2-way parallelizable and vectorizable.
    Can get away with 11 carries, but then data flow is much deeper.

    With tighter constraints on inputs can squeeze carries into int32.
*/

void fe_mul(fe h, const fe f, const fe g) {
    int32_t f0 = f[0];
    int32_t f1 = f[1];
    int32_t f2 = f[2];
    int32_t f3 = f[3];
    int32_t f4 = f[4];
    int32_t f5 = f[5];
    int32_t f6 = f[6];
    int32_t f7 = f[7];
    int32_t f8 = f[8];
    int32_t f9 = f[9];
    int32_t g0 = g[0];
    int32_t g1 = g[1];
    int32_t g2 = g[2];
    int32_t g3 = g[3];
    int32_t g4 = g[4];
    int32_t g5 = g[5];
    int32_t g6 = g[6];
    int32_t g7 = g[7];
    int32_t g8 = g[8];
    int32_t g9 = g[9];
    int32_t g1_19 = 19 * g1; /* 1.959375*2^29 */
    int32_t g2_19 = 19 * g2; /* 1.959375*2^30; still ok */
    int32_t g3_19 = 19 * g3;
    int32_t g4_19 = 19 * g4;
    int32_t g5_19 = 19 * g5;
    int32_t g6_19 = 19 * g6;
    int32_t g7_19 = 19 * g7;
    int32_t g8_19 = 19 * g8;
    int32_t g9_19 = 19 * g9;
    int32_t f1_2 = 2 * f1;
    int32_t f3_2 = 2 * f3;
    int32_t f5_2 = 2 * f5;
    int32_t f7_2 = 2 * f7;
    int32_t f9_2 = 2 * f9;
    int64_t f0g0    = f0   * (int64_t) g0;
    int64_t f0g1    = f0   * (int64_t) g1;
    int64_t f0g2    = f0   * (int64_t) g2;
    int64_t f0g3    = f0   * (int64_t) g3;
    int64_t f0g4    = f0   * (int64_t) g4;
    int64_t f0g5    = f0   * (int64_t) g5;
    int64_t f0g6    = f0   * (int64_t) g6;
    int64_t f0g7    = f0   * (int64_t) g7;
    int64_t f0g8    = f0   * (int64_t) g8;
    int64_t f0g9    = f0   * (int64_t) g9;
    int64_t f1g0    = f1   * (int64_t) g0;
    int64_t f1g1_2  = f1_2 * (int64_t) g1;
    int64_t f1g2    = f1   * (int64_t) g2;
    int64_t f1g3_2  = f1_2 * (int64_t) g3;
    int64_t f1g4    = f1   * (int64_t) g4;
    int64_t f1g5_2  = f1_2 * (int64_t) g5;
    int64_t f1g6    = f1   * (int64_t) g6;
    int64_t f1g7_2  = f1_2 * (int64_t) g7;
    int64_t f1g8    = f1   * (int64_t) g8;
    int64_t f1g9_38 = f1_2 * (int64_t) g9_19;
    int64_t f2g0    = f2   * (int64_t) g0;
    int64_t f2g1    = f2   * (int64_t) g1;
    int64_t f2g2    = f2   * (int64_t) g2;
    int64_t f2g3    = f2   * (int64_t) g3;
    int64_t f2g4    = f2   * (int64_t) g4;
    int64_t f2g5    = f2   * (int64_t) g5;
    int64_t f2g6    = f2   * (int64_t) g6;
    int64_t f2g7    = f2   * (int64_t) g7;
    int64_t f2g8_19 = f2   * (int64_t) g8_19;
    int64_t f2g9_19 = f2   * (int64_t) g9_19;
    int64_t f3g0    = f3   * (int64_t) g0;
    int64_t f3g1_2  = f3_2 * (int64_t) g1;
    int64_t f3g2    = f3   * (int64_t) g2;
    int64_t f3g3_2  = f3_2 * (int64_t) g3;
    int64_t f3g4    = f3   * (int64_t) g4;
    int64_t f3g5_2  = f3_2 * (int64_t) g5;
    int64_t f3g6    = f3   * (int64_t) g6;
    int64_t f3g7_38 = f3_2 * (int64_t) g7_19;
    int64_t f3g8_19 = f3   * (int64_t) g8_19;
    int64_t f3g9_38 = f3_2 * (int64_t) g9_19;
    int64_t f4g0    = f4   * (int64_t) g0;
    int64_t f4g1    = f4   * (int64_t) g1;
    int64_t f4g2    = f4   * (int64_t) g2;
    int64_t f4g3    = f4   * (int64_t) g3;
    int64_t f4g4    = f4   * (int64_t) g4;
    int64_t f4g5    = f4   * (int64_t) g5;
    int64_t f4g6_19 = f4   * (int64_t) g6_19;
    int64_t f4g7_19 = f4   * (int64_t) g7_19;
    int64_t f4g8_19 = f4   * (int64_t) g8_19;
    int64_t f4g9_19 = f4   * (int64_t) g9_19;
    int64_t f5g0    = f5   * (int64_t) g0;
    int64_t f5g1_2  = f5_2 * (int64_t) g1;
    int64_t f5g2    = f5   * (int64_t) g2;
    int64_t f5g3_2  = f5_2 * (int64_t) g3;
    int64_t f5g4    = f5   * (int64_t) g4;
    int64_t f5g5_38 = f5_2 * (int64_t) g5_19;
    int64_t f5g6_19 = f5   * (int64_t) g6_19;
    int64_t f5g7_38 = f5_2 * (int64_t) g7_19;
    int64_t f5g8_19 = f5   * (int64_t) g8_19;
    int64_t f5g9_38 = f5_2 * (int64_t) g9_19;
    int64_t f6g0    = f6   * (int64_t) g0;
    int64_t f6g1    = f6   * (int64_t) g1;
    int64_t f6g2    = f6   * (int64_t) g2;
    int64_t f6g3    = f6   * (int64_t) g3;
    int64_t f6g4_19 = f6   * (int64_t) g4_19;
    int64_t f6g5_19 = f6   * (int64_t) g5_19;
    int64_t f6g6_19 = f6   * (int64_t) g6_19;
    int64_t f6g7_19 = f6   * (int64_t) g7_19;
    int64_t f6g8_19 = f6   * (int64_t) g8_19;
    int64_t f6g9_19 = f6   * (int64_t) g9_19;
    int64_t f7g0    = f7   * (int64_t) g0;
    int64_t f7g1_2  = f7_2 * (int64_t) g1;
    int64_t f7g2    = f7   * (int64_t) g2;
    int64_t f7g3_38 = f7_2 * (int64_t) g3_19;
    int64_t f7g4_19 = f7   * (int64_t) g4_19;
    int64_t f7g5_38 = f7_2 * (int64_t) g5_19;
    int64_t f7g6_19 = f7   * (int64_t) g6_19;
    int64_t f7g7_38 = f7_2 * (int64_t) g7_19;
    int64_t f7g8_19 = f7   * (int64_t) g8_19;
    int64_t f7g9_38 = f7_2 * (int64_t) g9_19;
    int64_t f8g0    = f8   * (int64_t) g0;
    int64_t f8g1    = f8   * (int64_t) g1;
    int64_t f8g2_19 = f8   * (int64_t) g2_19;
    int64_t f8g3_19 = f8   * (int64_t) g3_19;
    int64_t f8g4_19 = f8   * (int64_t) g4_19;
    int64_t f8g5_19 = f8   * (int64_t) g5_19;
    int64_t f8g6_19 = f8   * (int64_t) g6_19;
    int64_t f8g7_19 = f8   * (int64_t) g7_19;
    int64_t f8g8_19 = f8   * (int64_t) g8_19;
    int64_t f8g9_19 = f8   * (int64_t) g9_19;
    int64_t f9g0    = f9   * (int64_t) g0;
    int64_t f9g1_38 = f9_2 * (int64_t) g1_19;
    int64_t f9g2_19 = f9   * (int64_t) g2_19;
    int64_t f9g3_38 = f9_2 * (int64_t) g3_19;
    int64_t f9g4_19 = f9   * (int64_t) g4_19;
    int64_t f9g5_38 = f9_2 * (int64_t) g5_19;
    int64_t f9g6_19 = f9   * (int64_t) g6_19;
    int64_t f9g7_38 = f9_2 * (int64_t) g7_19;
    int64_t f9g8_19 = f9   * (int64_t) g8_19;
    int64_t f9g9_38 = f9_2 * (int64_t) g9_19;
    int64_t h0 = f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 + f6g4_19 + f7g3_38 + f8g2_19 + f9g1_38;
    int64_t h1 = f0g1 + f1g0   + f2g9_19 + f3g8_19 + f4g7_19 + f5g6_19 + f6g5_19 + f7g4_19 + f8g3_19 + f9g2_19;
    int64_t h2 = f0g2 + f1g1_2 + f2g0   + f3g9_38 + f4g8_19 + f5g7_38 + f6g6_19 + f7g5_38 + f8g4_19 + f9g3_38;
    int64_t h3 = f0g3 + f1g2   + f2g1   + f3g0   + f4g9_19 + f5g8_19 + f6g7_19 + f7g6_19 + f8g5_19 + f9g4_19;
    int64_t h4 = f0g4 + f1g3_2 + f2g2   + f3g1_2 + f4g0   + f5g9_38 + f6g8_19 + f7g7_38 + f8g6_19 + f9g5_38;
    int64_t h5 = f0g5 + f1g4   + f2g3   + f3g2   + f4g1   + f5g0   + f6g9_19 + f7g8_19 + f8g7_19 + f9g6_19;
    int64_t h6 = f0g6 + f1g5_2 + f2g4   + f3g3_2 + f4g2   + f5g1_2 + f6g0   + f7g9_38 + f8g8_19 + f9g7_38;
    int64_t h7 = f0g7 + f1g6   + f2g5   + f3g4   + f4g3   + f5g2   + f6g1   + f7g0   + f8g9_19 + f9g8_19;
    int64_t h8 = f0g8 + f1g7_2 + f2g6   + f3g5_2 + f4g4   + f5g3_2 + f6g2   + f7g1_2 + f8g0   + f9g9_38;
    int64_t h9 = f0g9 + f1g8   + f2g7   + f3g6   + f4g5   + f5g4   + f6g3   + f7g2   + f8g1   + f9g0   ;
    int64_t carry0;
    int64_t carry1;
    int64_t carry2;
    int64_t carry3;
    int64_t carry4;
    int64_t carry5;
    int64_t carry6;
    int64_t carry7;
    int64_t carry8;
    int64_t carry9;

    carry0 = (h0 + (int64_t) (1 << 25)) >> 26;
    h1 += carry0;
    h0 -= carry0 << 26;
    carry4 = (h4 + (int64_t) (1 << 25)) >> 26;
    h5 += carry4;
    h4 -= carry4 << 26;

    carry1 = (h1 + (int64_t) (1 << 24)) >> 25;
    h2 += carry1;
    h1 -= carry1 << 25;
    carry5 = (h5 + (int64_t) (1 << 24)) >> 25;
    h6 += carry5;
    h5 -= carry5 << 25;

    carry2 = (h2 + (int64_t) (1 << 25)) >> 26;
    h3 += carry2;
    h2 -= carry2 << 26;
    carry6 = (h6 + (int64_t) (1 << 25)) >> 26;
    h7 += carry6;
    h6 -= carry6 << 26;

    carry3 = (h3 + (int64_t) (1 << 24)) >> 25;
    h4 += carry3;
    h3 -= carry3 << 25;
    carry7 = (h7 + (int64_t) (1 << 24)) >> 25;
    h8 += carry7;
    h7 -= carry7 << 25;

    carry4 = (h4 + (int64_t) (1 << 25)) >> 26;
    h5 += carry4;
    h4 -= carry4 << 26;
    carry8 = (h8 + (int64_t) (1 << 25)) >> 26;
    h9 += carry8;
    h8 -= carry8 << 26;

    carry9 = (h9 + (int64_t) (1 << 24)) >> 25;
    h0 += carry9 * 19;
    h9 -= carry9 << 25;

    carry0 = (h0 + (int64_t) (1 << 25)) >> 26;
    h1 += carry0;
    h0 -= carry0 << 26;

    h[0] = (int32_t) h0;
    h[1] = (int32_t) h1;
    h[2] = (int32_t) h2;
    h[3] = (int32_t) h3;
    h[4] = (int32_t) h4;
    h[5] = (int32_t) h5;
    h[6] = (int32_t) h6;
    h[7] = (int32_t) h7;
    h[8] = (int32_t) h8;
    h[9] = (int32_t) h9;
}


/*
h = f * 121666
Can overlap h with f.

Preconditions:
   |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.

Postconditions:
   |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
*/

void fe_mul121666(fe h, fe f) {
    int32_t f0 = f[0];
    int32_t f1 = f[1];
    int32_t f2 = f[2];
    int32_t f3 = f[3];
    int32_t f4 = f[4];
    int32_t f5 = f[5];
    int32_t f6 = f[6];
    int32_t f7 = f[7];
    int32_t f8 = f[8];
    int32_t f9 = f[9];
    int64_t h0 = f0 * (int64_t) 121666;
    int64_t h1 = f1 * (int64_t) 121666;
    int64_t h2 = f2 * (int64_t) 121666;
    int64_t h3 = f3 * (int64_t) 121666;
    int64_t h4 = f4 * (int64_t) 121666;
    int64_t h5 = f5 * (int64_t) 121666;
    int64_t h6 = f6 * (int64_t) 121666;
    int64_t h7 = f7 * (int64_t) 121666;
    int64_t h8 = f8 * (int64_t) 121666;
    int64_t h9 = f9 * (int64_t) 121666;
    int64_t carry0;
    int64_t carry1;
    int64_t carry2;
    int64_t carry3;
    int64_t carry4;
    int64_t carry5;
    int64_t carry6;
    int64_t carry7;
    int64_t carry8;
    int64_t carry9;

    carry9 = (h9 + (int64_t) (1<<24)) >> 25; h0 += carry9 * 19; h9 -= carry9 << 25;
    carry1 = (h1 + (int64_t) (1<<24)) >> 25; h2 += carry1; h1 -= carry1 << 25;
    carry3 = (h3 + (int64_t) (1<<24)) >> 25; h4 += carry3; h3 -= carry3 << 25;
    carry5 = (h5 + (int64_t) (1<<24)) >> 25; h6 += carry5; h5 -= carry5 << 25;
    carry7 = (h7 + (int64_t) (1<<24)) >> 25; h8 += carry7; h7 -= carry7 << 25;

    carry0 = (h0 + (int64_t) (1<<25)) >> 26; h1 += carry0; h0 -= carry0 << 26;
    carry2 = (h2 + (int64_t) (1<<25)) >> 26; h3 += carry2; h2 -= carry2 << 26;
    carry4 = (h4 + (int64_t) (1<<25)) >> 26; h5 += carry4; h4 -= carry4 << 26;
    carry6 = (h6 + (int64_t) (1<<25)) >> 26; h7 += carry6; h6 -= carry6 << 26;
    carry8 = (h8 + (int64_t) (1<<25)) >> 26; h9 += carry8; h8 -= carry8 << 26;

    h[0] = (int32_t) h0;
    h[1] = (int32_t) h1;
    h[2] = (int32_t) h2;
    h[3] = (int32_t) h3;
    h[4] = (int32_t) h4;
    h[5] = (int32_t) h5;
    h[6] = (int32_t) h6;
    h[7] = (int32_t) h7;
    h[8] = (int32_t) h8;
    h[9] = (int32_t) h9;
}


/*
h = -f

Preconditions:
   |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.

Postconditions:
   |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
*/

void fe_neg(fe h, const fe f) {
    int32_t f0 = f[0];
    int32_t f1 = f[1];
    int32_t f2 = f[2];
    int32_t f3 = f[3];
    int32_t f4 = f[4];
    int32_t f5 = f[5];
    int32_t f6 = f[6];
    int32_t f7 = f[7];
    int32_t f8 = f[8];
    int32_t f9 = f[9];
    int32_t h0 = -f0;
    int32_t h1 = -f1;
    int32_t h2 = -f2;
    int32_t h3 = -f3;
    int32_t h4 = -f4;
    int32_t h5 = -f5;
    int32_t h6 = -f6;
    int32_t h7 = -f7;
    int32_t h8 = -f8;
    int32_t h9 = -f9;

    h[0] = h0;
    h[1] = h1;
    h[2] = h2;
    h[3] = h3;
    h[4] = h4;
    h[5] = h5;
    h[6] = h6;
    h[7] = h7;
    h[8] = h8;
    h[9] = h9;
}


void fe_pow22523(fe out, const fe z) {
    fe t0;
    fe t1;
    fe t2;
    int i;
    fe_sq(t0, z);

    //for (i = 1; i < 1; ++i) { // Odd
    //    fe_sq(t0, t0);
    //}

    fe_sq(t1, t0);

    for (i = 1; i < 2; ++i) {
        fe_sq(t1, t1);
    }

    fe_mul(t1, z, t1);
    fe_mul(t0, t0, t1);
    fe_sq(t0, t0);

    //for (i = 1; i < 1; ++i) { // Odd
    //    fe_sq(t0, t0);
    //}

    fe_mul(t0, t1, t0);
    fe_sq(t1, t0);

    for (i = 1; i < 5; ++i) {
        fe_sq(t1, t1);
    }

    fe_mul(t0, t1, t0);
    fe_sq(t1, t0);

    for (i = 1; i < 10; ++i) {
        fe_sq(t1, t1);
    }

    fe_mul(t1, t1, t0);
    fe_sq(t2, t1);

    for (i = 1; i < 20; ++i) {
        fe_sq(t2, t2);
    }

    fe_mul(t1, t2, t1);
    fe_sq(t1, t1);

    for (i = 1; i < 10; ++i) {
        fe_sq(t1, t1);
    }

    fe_mul(t0, t1, t0);
    fe_sq(t1, t0);

    for (i = 1; i < 50; ++i) {
        fe_sq(t1, t1);
    }

    fe_mul(t1, t1, t0);
    fe_sq(t2, t1);

    for (i = 1; i < 100; ++i) {
        fe_sq(t2, t2);
    }

    fe_mul(t1, t2, t1);
    fe_sq(t1, t1);

    for (i = 1; i < 50; ++i) {
        fe_sq(t1, t1);
    }

    fe_mul(t0, t1, t0);
    fe_sq(t0, t0);

    for (i = 1; i < 2; ++i) {
        fe_sq(t0, t0);
    }

    fe_mul(out, t0, z);
    return;
}


/*
h = f * f
Can overlap h with f.

Preconditions:
   |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.

Postconditions:
   |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
*/

/*
See fe_mul.c for discussion of implementation strategy.
*/

void fe_sq(fe h, const fe f) {
    int32_t f0 = f[0];
    int32_t f1 = f[1];
    int32_t f2 = f[2];
    int32_t f3 = f[3];
    int32_t f4 = f[4];
    int32_t f5 = f[5];
    int32_t f6 = f[6];
    int32_t f7 = f[7];
    int32_t f8 = f[8];
    int32_t f9 = f[9];
    int32_t f0_2 = 2 * f0;
    int32_t f1_2 = 2 * f1;
    int32_t f2_2 = 2 * f2;
    int32_t f3_2 = 2 * f3;
    int32_t f4_2 = 2 * f4;
    int32_t f5_2 = 2 * f5;
    int32_t f6_2 = 2 * f6;
    int32_t f7_2 = 2 * f7;
    int32_t f5_38 = 38 * f5; /* 1.959375*2^30 */
    int32_t f6_19 = 19 * f6; /* 1.959375*2^30 */
    int32_t f7_38 = 38 * f7; /* 1.959375*2^30 */
    int32_t f8_19 = 19 * f8; /* 1.959375*2^30 */
    int32_t f9_38 = 38 * f9; /* 1.959375*2^30 */
    int64_t f0f0    = f0   * (int64_t) f0;
    int64_t f0f1_2  = f0_2 * (int64_t) f1;
    int64_t f0f2_2  = f0_2 * (int64_t) f2;
    int64_t f0f3_2  = f0_2 * (int64_t) f3;
    int64_t f0f4_2  = f0_2 * (int64_t) f4;
    int64_t f0f5_2  = f0_2 * (int64_t) f5;
    int64_t f0f6_2  = f0_2 * (int64_t) f6;
    int64_t f0f7_2  = f0_2 * (int64_t) f7;
    int64_t f0f8_2  = f0_2 * (int64_t) f8;
    int64_t f0f9_2  = f0_2 * (int64_t) f9;
    int64_t f1f1_2  = f1_2 * (int64_t) f1;
    int64_t f1f2_2  = f1_2 * (int64_t) f2;
    int64_t f1f3_4  = f1_2 * (int64_t) f3_2;
    int64_t f1f4_2  = f1_2 * (int64_t) f4;
    int64_t f1f5_4  = f1_2 * (int64_t) f5_2;
    int64_t f1f6_2  = f1_2 * (int64_t) f6;
    int64_t f1f7_4  = f1_2 * (int64_t) f7_2;
    int64_t f1f8_2  = f1_2 * (int64_t) f8;
    int64_t f1f9_76 = f1_2 * (int64_t) f9_38;
    int64_t f2f2    = f2   * (int64_t) f2;
    int64_t f2f3_2  = f2_2 * (int64_t) f3;
    int64_t f2f4_2  = f2_2 * (int64_t) f4;
    int64_t f2f5_2  = f2_2 * (int64_t) f5;
    int64_t f2f6_2  = f2_2 * (int64_t) f6;
    int64_t f2f7_2  = f2_2 * (int64_t) f7;
    int64_t f2f8_38 = f2_2 * (int64_t) f8_19;
    int64_t f2f9_38 = f2   * (int64_t) f9_38;
    int64_t f3f3_2  = f3_2 * (int64_t) f3;
    int64_t f3f4_2  = f3_2 * (int64_t) f4;
    int64_t f3f5_4  = f3_2 * (int64_t) f5_2;
    int64_t f3f6_2  = f3_2 * (int64_t) f6;
    int64_t f3f7_76 = f3_2 * (int64_t) f7_38;
    int64_t f3f8_38 = f3_2 * (int64_t) f8_19;
    int64_t f3f9_76 = f3_2 * (int64_t) f9_38;
    int64_t f4f4    = f4   * (int64_t) f4;
    int64_t f4f5_2  = f4_2 * (int64_t) f5;
    int64_t f4f6_38 = f4_2 * (int64_t) f6_19;
    int64_t f4f7_38 = f4   * (int64_t) f7_38;
    int64_t f4f8_38 = f4_2 * (int64_t) f8_19;
    int64_t f4f9_38 = f4   * (int64_t) f9_38;
    int64_t f5f5_38 = f5   * (int64_t) f5_38;
    int64_t f5f6_38 = f5_2 * (int64_t) f6_19;
    int64_t f5f7_76 = f5_2 * (int64_t) f7_38;
    int64_t f5f8_38 = f5_2 * (int64_t) f8_19;
    int64_t f5f9_76 = f5_2 * (int64_t) f9_38;
    int64_t f6f6_19 = f6   * (int64_t) f6_19;
    int64_t f6f7_38 = f6   * (int64_t) f7_38;
    int64_t f6f8_38 = f6_2 * (int64_t) f8_19;
    int64_t f6f9_38 = f6   * (int64_t) f9_38;
    int64_t f7f7_38 = f7   * (int64_t) f7_38;
    int64_t f7f8_38 = f7_2 * (int64_t) f8_19;
    int64_t f7f9_76 = f7_2 * (int64_t) f9_38;
    int64_t f8f8_19 = f8   * (int64_t) f8_19;
    int64_t f8f9_38 = f8   * (int64_t) f9_38;
    int64_t f9f9_38 = f9   * (int64_t) f9_38;
    int64_t h0 = f0f0  + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38;
    int64_t h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38;
    int64_t h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19;
    int64_t h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38;
    int64_t h4 = f0f4_2 + f1f3_4 + f2f2   + f5f9_76 + f6f8_38 + f7f7_38;
    int64_t h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38;
    int64_t h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19;
    int64_t h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38;
    int64_t h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4   + f9f9_38;
    int64_t h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2;
    int64_t carry0;
    int64_t carry1;
    int64_t carry2;
    int64_t carry3;
    int64_t carry4;
    int64_t carry5;
    int64_t carry6;
    int64_t carry7;
    int64_t carry8;
    int64_t carry9;
    carry0 = (h0 + (int64_t) (1 << 25)) >> 26;
    h1 += carry0;
    h0 -= carry0 << 26;
    carry4 = (h4 + (int64_t) (1 << 25)) >> 26;
    h5 += carry4;
    h4 -= carry4 << 26;
    carry1 = (h1 + (int64_t) (1 << 24)) >> 25;
    h2 += carry1;
    h1 -= carry1 << 25;
    carry5 = (h5 + (int64_t) (1 << 24)) >> 25;
    h6 += carry5;
    h5 -= carry5 << 25;
    carry2 = (h2 + (int64_t) (1 << 25)) >> 26;
    h3 += carry2;
    h2 -= carry2 << 26;
    carry6 = (h6 + (int64_t) (1 << 25)) >> 26;
    h7 += carry6;
    h6 -= carry6 << 26;
    carry3 = (h3 + (int64_t) (1 << 24)) >> 25;
    h4 += carry3;
    h3 -= carry3 << 25;
    carry7 = (h7 + (int64_t) (1 << 24)) >> 25;
    h8 += carry7;
    h7 -= carry7 << 25;
    carry4 = (h4 + (int64_t) (1 << 25)) >> 26;
    h5 += carry4;
    h4 -= carry4 << 26;
    carry8 = (h8 + (int64_t) (1 << 25)) >> 26;
    h9 += carry8;
    h8 -= carry8 << 26;
    carry9 = (h9 + (int64_t) (1 << 24)) >> 25;
    h0 += carry9 * 19;
    h9 -= carry9 << 25;
    carry0 = (h0 + (int64_t) (1 << 25)) >> 26;
    h1 += carry0;
    h0 -= carry0 << 26;
    h[0] = (int32_t) h0;
    h[1] = (int32_t) h1;
    h[2] = (int32_t) h2;
    h[3] = (int32_t) h3;
    h[4] = (int32_t) h4;
    h[5] = (int32_t) h5;
    h[6] = (int32_t) h6;
    h[7] = (int32_t) h7;
    h[8] = (int32_t) h8;
    h[9] = (int32_t) h9;
}


/*
h = 2 * f * f
Can overlap h with f.

Preconditions:
   |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.

Postconditions:
   |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
*/

/*
See fe_mul.c for discussion of implementation strategy.
*/

void fe_sq2(fe h, const fe f) {
    int32_t f0 = f[0];
    int32_t f1 = f[1];
    int32_t f2 = f[2];
    int32_t f3 = f[3];
    int32_t f4 = f[4];
    int32_t f5 = f[5];
    int32_t f6 = f[6];
    int32_t f7 = f[7];
    int32_t f8 = f[8];
    int32_t f9 = f[9];
    int32_t f0_2 = 2 * f0;
    int32_t f1_2 = 2 * f1;
    int32_t f2_2 = 2 * f2;
    int32_t f3_2 = 2 * f3;
    int32_t f4_2 = 2 * f4;
    int32_t f5_2 = 2 * f5;
    int32_t f6_2 = 2 * f6;
    int32_t f7_2 = 2 * f7;
    int32_t f5_38 = 38 * f5; /* 1.959375*2^30 */
    int32_t f6_19 = 19 * f6; /* 1.959375*2^30 */
    int32_t f7_38 = 38 * f7; /* 1.959375*2^30 */
    int32_t f8_19 = 19 * f8; /* 1.959375*2^30 */
    int32_t f9_38 = 38 * f9; /* 1.959375*2^30 */
    int64_t f0f0    = f0   * (int64_t) f0;
    int64_t f0f1_2  = f0_2 * (int64_t) f1;
    int64_t f0f2_2  = f0_2 * (int64_t) f2;
    int64_t f0f3_2  = f0_2 * (int64_t) f3;
    int64_t f0f4_2  = f0_2 * (int64_t) f4;
    int64_t f0f5_2  = f0_2 * (int64_t) f5;
    int64_t f0f6_2  = f0_2 * (int64_t) f6;
    int64_t f0f7_2  = f0_2 * (int64_t) f7;
    int64_t f0f8_2  = f0_2 * (int64_t) f8;
    int64_t f0f9_2  = f0_2 * (int64_t) f9;
    int64_t f1f1_2  = f1_2 * (int64_t) f1;
    int64_t f1f2_2  = f1_2 * (int64_t) f2;
    int64_t f1f3_4  = f1_2 * (int64_t) f3_2;
    int64_t f1f4_2  = f1_2 * (int64_t) f4;
    int64_t f1f5_4  = f1_2 * (int64_t) f5_2;
    int64_t f1f6_2  = f1_2 * (int64_t) f6;
    int64_t f1f7_4  = f1_2 * (int64_t) f7_2;
    int64_t f1f8_2  = f1_2 * (int64_t) f8;
    int64_t f1f9_76 = f1_2 * (int64_t) f9_38;
    int64_t f2f2    = f2   * (int64_t) f2;
    int64_t f2f3_2  = f2_2 * (int64_t) f3;
    int64_t f2f4_2  = f2_2 * (int64_t) f4;
    int64_t f2f5_2  = f2_2 * (int64_t) f5;
    int64_t f2f6_2  = f2_2 * (int64_t) f6;
    int64_t f2f7_2  = f2_2 * (int64_t) f7;
    int64_t f2f8_38 = f2_2 * (int64_t) f8_19;
    int64_t f2f9_38 = f2   * (int64_t) f9_38;
    int64_t f3f3_2  = f3_2 * (int64_t) f3;
    int64_t f3f4_2  = f3_2 * (int64_t) f4;
    int64_t f3f5_4  = f3_2 * (int64_t) f5_2;
    int64_t f3f6_2  = f3_2 * (int64_t) f6;
    int64_t f3f7_76 = f3_2 * (int64_t) f7_38;
    int64_t f3f8_38 = f3_2 * (int64_t) f8_19;
    int64_t f3f9_76 = f3_2 * (int64_t) f9_38;
    int64_t f4f4    = f4   * (int64_t) f4;
    int64_t f4f5_2  = f4_2 * (int64_t) f5;
    int64_t f4f6_38 = f4_2 * (int64_t) f6_19;
    int64_t f4f7_38 = f4   * (int64_t) f7_38;
    int64_t f4f8_38 = f4_2 * (int64_t) f8_19;
    int64_t f4f9_38 = f4   * (int64_t) f9_38;
    int64_t f5f5_38 = f5   * (int64_t) f5_38;
    int64_t f5f6_38 = f5_2 * (int64_t) f6_19;
    int64_t f5f7_76 = f5_2 * (int64_t) f7_38;
    int64_t f5f8_38 = f5_2 * (int64_t) f8_19;
    int64_t f5f9_76 = f5_2 * (int64_t) f9_38;
    int64_t f6f6_19 = f6   * (int64_t) f6_19;
    int64_t f6f7_38 = f6   * (int64_t) f7_38;
    int64_t f6f8_38 = f6_2 * (int64_t) f8_19;
    int64_t f6f9_38 = f6   * (int64_t) f9_38;
    int64_t f7f7_38 = f7   * (int64_t) f7_38;
    int64_t f7f8_38 = f7_2 * (int64_t) f8_19;
    int64_t f7f9_76 = f7_2 * (int64_t) f9_38;
    int64_t f8f8_19 = f8   * (int64_t) f8_19;
    int64_t f8f9_38 = f8   * (int64_t) f9_38;
    int64_t f9f9_38 = f9   * (int64_t) f9_38;
    int64_t h0 = f0f0  + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38;
    int64_t h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38;
    int64_t h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19;
    int64_t h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38;
    int64_t h4 = f0f4_2 + f1f3_4 + f2f2   + f5f9_76 + f6f8_38 + f7f7_38;
    int64_t h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38;
    int64_t h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19;
    int64_t h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38;
    int64_t h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4   + f9f9_38;
    int64_t h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2;
    int64_t carry0;
    int64_t carry1;
    int64_t carry2;
    int64_t carry3;
    int64_t carry4;
    int64_t carry5;
    int64_t carry6;
    int64_t carry7;
    int64_t carry8;
    int64_t carry9;
    h0 += h0;
    h1 += h1;
    h2 += h2;
    h3 += h3;
    h4 += h4;
    h5 += h5;
    h6 += h6;
    h7 += h7;
    h8 += h8;
    h9 += h9;
    carry0 = (h0 + (int64_t) (1 << 25)) >> 26;
    h1 += carry0;
    h0 -= carry0 << 26;
    carry4 = (h4 + (int64_t) (1 << 25)) >> 26;
    h5 += carry4;
    h4 -= carry4 << 26;
    carry1 = (h1 + (int64_t) (1 << 24)) >> 25;
    h2 += carry1;
    h1 -= carry1 << 25;
    carry5 = (h5 + (int64_t) (1 << 24)) >> 25;
    h6 += carry5;
    h5 -= carry5 << 25;
    carry2 = (h2 + (int64_t) (1 << 25)) >> 26;
    h3 += carry2;
    h2 -= carry2 << 26;
    carry6 = (h6 + (int64_t) (1 << 25)) >> 26;
    h7 += carry6;
    h6 -= carry6 << 26;
    carry3 = (h3 + (int64_t) (1 << 24)) >> 25;
    h4 += carry3;
    h3 -= carry3 << 25;
    carry7 = (h7 + (int64_t) (1 << 24)) >> 25;
    h8 += carry7;
    h7 -= carry7 << 25;
    carry4 = (h4 + (int64_t) (1 << 25)) >> 26;
    h5 += carry4;
    h4 -= carry4 << 26;
    carry8 = (h8 + (int64_t) (1 << 25)) >> 26;
    h9 += carry8;
    h8 -= carry8 << 26;
    carry9 = (h9 + (int64_t) (1 << 24)) >> 25;
    h0 += carry9 * 19;
    h9 -= carry9 << 25;
    carry0 = (h0 + (int64_t) (1 << 25)) >> 26;
    h1 += carry0;
    h0 -= carry0 << 26;
    h[0] = (int32_t) h0;
    h[1] = (int32_t) h1;
    h[2] = (int32_t) h2;
    h[3] = (int32_t) h3;
    h[4] = (int32_t) h4;
    h[5] = (int32_t) h5;
    h[6] = (int32_t) h6;
    h[7] = (int32_t) h7;
    h[8] = (int32_t) h8;
    h[9] = (int32_t) h9;
}


/*
h = f - g
Can overlap h with f or g.

Preconditions:
   |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
   |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.

Postconditions:
   |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
*/

void fe_sub(fe h, const fe f, const fe g) {
    int32_t f0 = f[0];
    int32_t f1 = f[1];
    int32_t f2 = f[2];
    int32_t f3 = f[3];
    int32_t f4 = f[4];
    int32_t f5 = f[5];
    int32_t f6 = f[6];
    int32_t f7 = f[7];
    int32_t f8 = f[8];
    int32_t f9 = f[9];
    int32_t g0 = g[0];
    int32_t g1 = g[1];
    int32_t g2 = g[2];
    int32_t g3 = g[3];
    int32_t g4 = g[4];
    int32_t g5 = g[5];
    int32_t g6 = g[6];
    int32_t g7 = g[7];
    int32_t g8 = g[8];
    int32_t g9 = g[9];
    int32_t h0 = f0 - g0;
    int32_t h1 = f1 - g1;
    int32_t h2 = f2 - g2;
    int32_t h3 = f3 - g3;
    int32_t h4 = f4 - g4;
    int32_t h5 = f5 - g5;
    int32_t h6 = f6 - g6;
    int32_t h7 = f7 - g7;
    int32_t h8 = f8 - g8;
    int32_t h9 = f9 - g9;

    h[0] = h0;
    h[1] = h1;
    h[2] = h2;
    h[3] = h3;
    h[4] = h4;
    h[5] = h5;
    h[6] = h6;
    h[7] = h7;
    h[8] = h8;
    h[9] = h9;
}



/*
Preconditions:
  |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.

Write p=2^255-19; q=floor(h/p).
Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))).

Proof:
  Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4.
  Also have |h-2^230 h9|<2^231 so |19 2^(-255)(h-2^230 h9)|<1/4.

  Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9).
  Then 0<y<1.

  Write r=h-pq.
  Have 0<=r<=p-1=2^255-20.
  Thus 0<=r+19(2^-255)r<r+19(2^-255)2^255<=2^255-1.

  Write x=r+19(2^-255)r+y.
  Then 0<x<2^255 so floor(2^(-255)x) = 0 so floor(q+2^(-255)x) = q.

  Have q+2^(-255)x = 2^(-255)(h + 19 2^(-25) h9 + 2^(-1))
  so floor(2^(-255)(h + 19 2^(-25) h9 + 2^(-1))) = q.
*/

void fe_tobytes(unsigned char *s, const fe h) {
    int32_t h0 = h[0];
    int32_t h1 = h[1];
    int32_t h2 = h[2];
    int32_t h3 = h[3];
    int32_t h4 = h[4];
    int32_t h5 = h[5];
    int32_t h6 = h[6];
    int32_t h7 = h[7];
    int32_t h8 = h[8];
    int32_t h9 = h[9];
    int32_t q;
    int32_t carry0;
    int32_t carry1;
    int32_t carry2;
    int32_t carry3;
    int32_t carry4;
    int32_t carry5;
    int32_t carry6;
    int32_t carry7;
    int32_t carry8;
    int32_t carry9;
    q = (19 * h9 + (((int32_t) 1) << 24)) >> 25;
    q = (h0 + q) >> 26;
    q = (h1 + q) >> 25;
    q = (h2 + q) >> 26;
    q = (h3 + q) >> 25;
    q = (h4 + q) >> 26;
    q = (h5 + q) >> 25;
    q = (h6 + q) >> 26;
    q = (h7 + q) >> 25;
    q = (h8 + q) >> 26;
    q = (h9 + q) >> 25;
    /* Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. */
    h0 += 19 * q;
    /* Goal: Output h-2^255 q, which is between 0 and 2^255-20. */
    carry0 = h0 >> 26;
    h1 += carry0;
    h0 -= carry0 << 26;
    carry1 = h1 >> 25;
    h2 += carry1;
    h1 -= carry1 << 25;
    carry2 = h2 >> 26;
    h3 += carry2;
    h2 -= carry2 << 26;
    carry3 = h3 >> 25;
    h4 += carry3;
    h3 -= carry3 << 25;
    carry4 = h4 >> 26;
    h5 += carry4;
    h4 -= carry4 << 26;
    carry5 = h5 >> 25;
    h6 += carry5;
    h5 -= carry5 << 25;
    carry6 = h6 >> 26;
    h7 += carry6;
    h6 -= carry6 << 26;
    carry7 = h7 >> 25;
    h8 += carry7;
    h7 -= carry7 << 25;
    carry8 = h8 >> 26;
    h9 += carry8;
    h8 -= carry8 << 26;
    carry9 = h9 >> 25;
    h9 -= carry9 << 25;

    /* h10 = carry9 */
    /*
    Goal: Output h0+...+2^255 h10-2^255 q, which is between 0 and 2^255-20.
    Have h0+...+2^230 h9 between 0 and 2^255-1;
    evidently 2^255 h10-2^255 q = 0.
    Goal: Output h0+...+2^230 h9.
    */
    s[0] = (unsigned char) (h0 >> 0);
    s[1] = (unsigned char) (h0 >> 8);
    s[2] = (unsigned char) (h0 >> 16);
    s[3] = (unsigned char) ((h0 >> 24) | (h1 << 2));
    s[4] = (unsigned char) (h1 >> 6);
    s[5] = (unsigned char) (h1 >> 14);
    s[6] = (unsigned char) ((h1 >> 22) | (h2 << 3));
    s[7] = (unsigned char) (h2 >> 5);
    s[8] = (unsigned char) (h2 >> 13);
    s[9] = (unsigned char) ((h2 >> 21) | (h3 << 5));
    s[10] = (unsigned char) (h3 >> 3);
    s[11] = (unsigned char) (h3 >> 11);
    s[12] = (unsigned char) ((h3 >> 19) | (h4 << 6));
    s[13] = (unsigned char) (h4 >> 2);
    s[14] = (unsigned char) (h4 >> 10);
    s[15] = (unsigned char) (h4 >> 18);
    s[16] = (unsigned char) (h5 >> 0);
    s[17] = (unsigned char) (h5 >> 8);
    s[18] = (unsigned char) (h5 >> 16);
    s[19] = (unsigned char) ((h5 >> 24) | (h6 << 1));
    s[20] = (unsigned char) (h6 >> 7);
    s[21] = (unsigned char) (h6 >> 15);
    s[22] = (unsigned char) ((h6 >> 23) | (h7 << 3));
    s[23] = (unsigned char) (h7 >> 5);
    s[24] = (unsigned char) (h7 >> 13);
    s[25] = (unsigned char) ((h7 >> 21) | (h8 << 4));
    s[26] = (unsigned char) (h8 >> 4);
    s[27] = (unsigned char) (h8 >> 12);
    s[28] = (unsigned char) ((h8 >> 20) | (h9 << 6));
    s[29] = (unsigned char) (h9 >> 2);
    s[30] = (unsigned char) (h9 >> 10);
    s[31] = (unsigned char) (h9 >> 18);
}
/*end copyright*/
#endif // FOOP_FE
#endif // USE_ED25519



#ifdef DO_WIFI
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include <time.h>
#include <sys/time.h>
#include "esp_attr.h"
#include "esp_sleep.h"
#include "esp_netif_sntp.h"
#include "lwip/ip_addr.h"
#include "esp_sntp.h"
#include "lwip/err.h"
#include "lwip/sys.h"

#define ESP_MAXIMUM_RETRY  3
static int s_retry_num = 0;
enum {MYWIFI_DEINIT,MYWIFI_INITIALIZED,MYWIFI_CONFIGURED,MYWIFI_STARTED,MYWIFI_CONNECTING,MYWIFI_CONNECTED,MYWIFI_FAILED,MYWIFI_STOPPED};
static uint8_t mywifi_stat=MYWIFI_DEINIT;
static void event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
{
  if(event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
    esp_wifi_connect();
    mywifi_stat = MYWIFI_CONNECTING;
  } else if(event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
    if(s_retry_num < ESP_MAXIMUM_RETRY) {
      esp_wifi_connect();
      s_retry_num++;
      myDprintf("retry to connect to the AP\n");
    } else {
      mywifi_stat = MYWIFI_FAILED;
      haveinet = 0;
      myDprintf("Failed to connect to SSID:%s\n",ssid?ssid:"null");
    }
  } else if(event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
    ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
    myDprintf("got ip:"IPSTR"\n",IP2STR(&event->ip_info.ip));
    s_retry_num = 0;
    mywifi_stat = MYWIFI_CONNECTED;
    haveinet = 1;
  }
}
static int mywificonnect(int flg)
{
  if(flg == 1) {
    ESP_ERROR_CHECK(esp_wifi_stop());
    mywifi_stat = MYWIFI_STOPPED;
    haveinet = 0;
    return 0;
  }
  if(mywifi_stat == MYWIFI_CONNECTED) return 0; // connected
  if(mywifi_stat == MYWIFI_DEINIT) {
    esp_err_t ret = nvs_flash_init();
    if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();
    
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
							ESP_EVENT_ANY_ID,
							&event_handler,
							NULL,
							&instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
							IP_EVENT_STA_GOT_IP,
							&event_handler,
							NULL,
							&instance_got_ip));

    mywifi_stat = MYWIFI_INITIALIZED;
  }
  
  if(mywifi_stat == MYWIFI_INITIALIZED) {
    wifi_config_t wifi_config = {
      .sta = {
	//.ssid = ssid,
	//.password = wifipass,
	/* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (password len => 8).
	 * If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value
	 * to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to
	 * WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards.
	 */
	//.threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,	
	//.sae_pwe_h2e = ESP_WIFI_SAE_MODE,
	//.sae_h2e_identifier = EXAMPLE_H2E_IDENTIFIER,	
      },
    };
    if(ssid) strncpy((char *)wifi_config.sta.ssid,ssid,min(sizeof(wifi_config.sta.ssid),strlen(ssid)+1));
    if(wifipass) strncpy((char *)wifi_config.sta.password,wifipass,min(sizeof(wifi_config.sta.password),strlen(wifipass)+1));
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA,&wifi_config) );
    myDprintf("wifi_init_sta finished.\n");
    mywifi_stat = MYWIFI_CONFIGURED;
  }

  haveinet = 0;
  
  ESP_ERROR_CHECK(esp_wifi_start());
  mywifi_stat = MYWIFI_STARTED;
  
  return 0;
}
static int dowifi(int argc,char *argv[])
{
  if(argc == 1) {
    myprintf("%s\n",haveinet?"connected":"not connected");
    return 0;
  }
  if(strcmp(argv[1],"start") == 0) mywificonnect(0);
  else if(strcmp(argv[1],"stop") == 0) mywificonnect(1);  
  else if(strcmp(argv[1],"ssid") == 0 && argc == 3) {
    if(ssid) myfree(ssid);
    ssid = mystrdup(argv[2]);
    if(dodiskstuff == NULL) dodiskstuff = mystrdup("update");
  } else if(strcmp(argv[1],"pass") == 0 && argc == 3) {
    if(wifipass) myfree(wifipass);
    wifipass = mystrdup(argv[2]);
    if(dodiskstuff == NULL) dodiskstuff = mystrdup("update");
  } else { myprintf("Unknown command\n"); }
  return 0;
}

static int64_t ntpdiff=0;
void sntp_sync_time(struct timeval *tv)
{
  struct timeval tv2;
  gettimeofday(&tv2,NULL);
  //settimeofday(tv,NULL);
  int64_t tt1=(tv->tv_sec*1000000) + tv->tv_usec;
  int64_t tt2=(tv2.tv_sec*1000000) + tv2.tv_usec;
  ntpdiff = tt2 - tt1;
  sntp_set_sync_status(SNTP_SYNC_STATUS_COMPLETED);
}
static void ntpproc(int flg)
{
  if(haveinet == 0) {
    //myprintf("No Net\n");
    return;
  }
  if(ntpserver == NULL) {
    //myprintf("NTP server NULL\n");
    return;
  }
  switch(flg) {
  case 1:
    esp_netif_sntp_deinit();
    myDprintf("stopped NTP\n");
    return;
  case 2:
    if(ntpdiff) myDprintf("me-ntp=%ldus\n",ntpdiff);
    ntpdiff = 0;
    return;
  case 0:
    break; // fallthough and start
  }
  esp_sntp_config_t config=ESP_NETIF_SNTP_DEFAULT_CONFIG(ntpserver);
  //config.sync_cb = time_sync_notification_cb;
  esp_netif_sntp_init(&config);
  sntp_set_sync_interval(5000); // ms
  myDprintf("started NTP\n");
}
static int dontp(int argc,char *argv[])
{
  if(argc == 1) { ntpproc(2); return 0; } // show me-mtp time
  if(strcmp(argv[1],"start") == 0) {
    if(haveinet == 0) {
      myprintf("No Net\n");
      //mywificonnect(0);
      return 0;
    }
    if(ntpserver == NULL) {
      myprintf("NTP server NULL\n");
      return 0;
    }
    ntpproc(0); // start ntp client    
  } else if(strcmp(argv[1],"stop") == 0) ntpproc(1);
  else if(strcmp(argv[1],"server") == 0 && argc == 3) {
    if(ntpserver) myfree(ntpserver);
    ntpserver = mystrdup(argv[2]);
    if(dodiskstuff == NULL) dodiskstuff = mystrdup("update");
  } else { myprintf("Unknown command\n"); }  
  return 0;
}
#endif // DO_WIFI



#ifdef DO_FFT
#include "../../dofft.c"
#endif // DO_FFT
