/****************************************************************************
 * apps/system/ofloader/ofloader.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <inttypes.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <mqueue.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/boardctl.h>

#include "ofloader.h"

/****************************************************************************
 * Private Type Definitions
 ****************************************************************************/

struct devinfo_s
{
  int fd;
  off_t pos;
  off_t base;
  size_t size;
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static int get_partinfo(FAR struct devinfo_s *devinfo)
{
  struct partition_info_s info;
  int ret;

  ret = ioctl(devinfo->fd, BIOC_PARTINFO, &info);
  if (ret < 0)
    {
      return -errno;
    }

  devinfo->base = info.sectorsize * info.startsector;
  devinfo->size = info.sectorsize * info.numsectors;
  return 0;
}

static void free_devinfo(FAR struct devinfo_s *devinfo, size_t count)
{
  if (devinfo == NULL || count == 0)
    {
      return;
    }

  while (count-- != 0)
    {
      if (devinfo[count].fd > 0)
        {
          close(devinfo[count].fd);
        }
    }

  free(devinfo);
}

static FAR struct devinfo_s *parse_devinfo(FAR size_t *index)
{
  char table[] = CONFIG_SYSTEM_OFLOADER_TABLE;
  FAR struct devinfo_s *devinfo = NULL;
  FAR struct devinfo_s *tmp;
  FAR char *save_once;
  FAR char *save;
  FAR char *p;

  *index = 0;
  p = strtok_r(table, ";", &save_once);
  while (p != NULL)
    {
      p = strtok_r(p, ",", &save);
      if (p == NULL)
        {
          goto out;
        }

      tmp = realloc(devinfo, (*index + 1) * sizeof(struct devinfo_s));
      if (tmp == NULL)
        {
          goto out;
        }

      devinfo = tmp;
      devinfo[*index].fd = open(p, O_RDWR);
      if (devinfo[*index].fd < 0)
        {
          goto out;
        }

      p = strtok_r(NULL, ",", &save);
      devinfo[*index].base = strtoul(p, NULL, 0);
      if (devinfo[*index].base == 0)
        {
          if (get_partinfo(&devinfo[*index]) < 0)
            {
              (*index)++;
              goto out;
            }

          goto again;
        }

      p = strtok_r(NULL, ",", &save);
      if (p == NULL)
        {
          (*index)++;
          goto out;
        }

      devinfo[*index].size = strtoul(p, NULL, 0);
      if (devinfo[*index].size == 0)
        {
          (*index)++;
          goto out;
        }

again:
      devinfo[*index].pos = devinfo[*index].base;
      p = strtok_r(NULL, ";", &save_once);
      (*index)++;
    }

  return devinfo;

out:
  free_devinfo(devinfo, *index);
  return NULL;
}

static ssize_t read_safe(int fd, FAR uint8_t *buff, size_t size)
{
  ssize_t i = 0;

  while (i < size)
    {
      ssize_t ret = read(fd, buff + i, size - i);
      if (ret < 0)
        {
          return -errno;
        }

      i += ret;
    }

  return i;
}

static ssize_t write_safe(int fd, FAR uint8_t *buff, size_t size)
{
  ssize_t i = 0;

  while (i < size)
    {
      ssize_t ret = write(fd, buff + i, size - i);
      if (ret < 0)
        {
          return -errno;
        }

      i += ret;
    }

  return i;
}

static ssize_t handle(FAR struct ofloader_msg *msg,
                      FAR struct devinfo_s *devinfo, size_t count)
{
  FAR uint8_t *buff = NULL;
  ssize_t ret = 0;
  size_t index;

  for (index = 0; index < count; index++)
    {
      if (msg->addr >= devinfo[index].base &&
          msg->addr < devinfo[index].base + devinfo[index].size)
        {
          break;
        }
    }

  if (index == count)
    {
      return -ENODEV;
    }

  if (devinfo[index].pos != msg->addr)
    {
      off_t off = msg->addr - devinfo[index].pos;

      off = lseek(devinfo[index].fd, off, SEEK_CUR);
      if (off < 0)
        {
          OFLOADER_DEBUG("lseek error\n");
          return -errno;
        }

      devinfo[index].pos = msg->addr;
    }

  OFLOADER_DEBUG("index %zu pos %" PRIxOFF "\n", index, devinfo[index].pos);
  OFLOADER_DEBUG("atcion %d addr %" PRIxOFF "size %zu\n",
                 msg->atcion, msg->addr, msg->size);
  switch (msg->atcion)
    {
      case OFLOADER_SYNC:
        ret = fsync(devinfo[index].fd);
        if (ret < 0)
          {
            ret = -errno;
          }

        break;
      case OFLOADER_VERIFY:
        buff = malloc(msg->size);
        if (buff == NULL)
          {
            OFLOADER_DEBUG("verify no mem\n");
            return -ENOMEM;
          }

        ret = read_safe(devinfo[index].fd, buff, msg->size);
        if (ret < 0)
          {
            OFLOADER_DEBUG("verify read error\n");
            free(buff);
            break;
          }

        devinfo[index].pos += ret;
        if (memcmp(msg->buff, buff, ret) != 0)
          {
            OFLOADER_DEBUG("verify error\n");
            ret = -EIO;
            free(buff);
            break;
          }

        free(buff);
        break;
      case OFLOADER_READ:
        ret = read_safe(devinfo[index].fd, msg->buff, msg->size);
        if (ret < 0)
          {
            OFLOADER_DEBUG("read error\n");
            break;
          }

        devinfo[index].pos += ret;
        break;
      case OFLOADER_WRITE:
        ret = write_safe(devinfo[index].fd, msg->buff, msg->size);
        if (ret < 0)
          {
            OFLOADER_DEBUG("write error\n");
            break;
          }

        devinfo[index].pos += ret;
        break;
    }

  return ret;
}

static pthread_addr_t fake_idle(pthread_addr_t arg)
{
  for (; ; )
    {
    }

  return NULL;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * ofl_main
 ****************************************************************************/

int main(int argc, FAR char *argv[])
{
  FAR struct devinfo_s *devinfo;
  FAR struct ofloader_msg *msg;
  struct mq_attr mqattr;
  pthread_attr_t attr;
  pthread_t thread;
  size_t count;
  size_t i;
  mqd_t mq;

#if defined(CONFIG_BOARDCTL) && !defined(CONFIG_NSH_ARCHINIT)
  /* Perform architecture-specific initialization (if configured) */

  boardctl(BOARDIOC_INIT, 0);

#  ifdef CONFIG_BOARDCTL_FINALINIT
  /* Perform architecture-specific final-initialization (if configured) */

  boardctl(BOARDIOC_FINALINIT, 0);
#  endif
#endif

  memset(&mqattr, 0, sizeof(struct mq_attr));
  mqattr.mq_msgsize = sizeof(msg);
  mqattr.mq_maxmsg  = 10;

  if (g_create_idle)
    {
      pthread_attr_init(&attr);
      attr.priority = 1;
      pthread_create(&thread, &attr, fake_idle, NULL);
      pthread_attr_destroy(&attr);
    }

  mq = mq_open(OFLOADER_QNAME, O_CREAT | O_RDWR, 0660, &mqattr);
  if (mq < 0)
    {
      OFLOADER_DEBUG("mq_open error:%d\n", errno);
      return -errno;
    }

  devinfo = parse_devinfo(&count);
  if (devinfo == NULL)
    {
      OFLOADER_DEBUG("parsdevinfo error\n");
      mq_close(mq);
      return -EINVAL;
    }

  for (i = 0; i < count; i++)
    {
      OFLOADER_DEBUG("seq %d, base %" PRIxOFF ", size %zx\n",
                     i, devinfo[i].base, devinfo[i].size);
    }

  while (1)
    {
      if (mq_receive(mq, (FAR char *)&msg, sizeof(msg), NULL) < 0)
        {
          OFLOADER_DEBUG(" mq_receive error %d\n", -errno);
          continue;
        }

      if (handle(msg, devinfo, count) < 0)
        {
          msg->atcion = OFLOADER_ERROR;
        }
      else
        {
          msg->atcion = OFLOADER_FINSH;
        }
    }

  /* Never run to here */

  free_devinfo(devinfo, count);
  mq_close(mq);
  return 0;
}