/*
 * ALSA SoundScape control utility
 *
 * Copyright (c) 2003 by Chris Rankin
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

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

#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/ioctl.h>

#include <alsa/sound/sscape_ioctl.h>
#include <alsa/asoundlib.h>


const char default_dir[] = "/sndscape";
const char scope[] = "scope.cod";
unsigned char _microcode[SSCAPE_MICROCODE_SIZE];

static void
show_usage(void)
{
  printf("sscape_ctl: [--card card number]\n"
         "            [--directory firmware directory]\n"
         "sscape_ctl: --help\n"
         "sscape_ctl: --version\n");
}


static void
show_version(void)
{
  printf("ALSA SoundScape control utility: v" VERSION "\n");
}


void
safe_close(int fd)
{
  int err;
  while (((err = close(fd)) != 0) && (errno == EINTR)) {}
}

size_t
get_directory(const char *dir, char *buffer, size_t bufsize)
{
  size_t len;

  len = snprintf(buffer, bufsize, "%s/", dir);
  if (len >= bufsize)
    return 0;

  if ((len > 1) && (buffer[len - 1] == '/') && (buffer[len - 2] == '/'))
  {
    buffer[--len] = '\0';
  }

  return len;
}


size_t
get_bootfile(const char *filename, char *buffer, size_t bufsize)
{
  size_t len = snprintf(buffer, bufsize, "%s", filename);
  if (len >= bufsize)
    return 0;
  return len;
}


size_t
get_mcodefile(unsigned version, char *buffer, size_t bufsize)
{
  static const char sndscape[] = "sndscape.co%u";

  size_t len = snprintf(buffer, bufsize, sndscape, version);
  if (len >= bufsize)
    return 0;
  return len;
}


int
load_bootblock(const char *fname, struct sscape_bootblock *boot)
{
  int err;
  int fd;

  printf("Bootblock: %s\n", fname);

  err = fd = open(fname, O_RDONLY);
  if (err >= 0)
  {
    int save_errno;

    err = read(fd, boot->code, sizeof(boot->code));
    if (err >= 0)
    {
      printf("Bootblock: read %d bytes\n", err);
      err = 0;
    }

    save_errno = errno;
    safe_close(fd);
    errno = save_errno;
  }

  return err;
}


int
load_microcode(const char *fname, struct sscape_microcode *microcode)
{
  int err;
  int fd;

  printf("Microcode: %s\n", fname);

  err = fd = open(fname, O_RDONLY);
  if (err >= 0)
  {
    int save_errno;

    err = read(fd, microcode->code, sizeof(_microcode));
    if (err >= 0)
    {
      printf("Microcode: read %d bytes\n", err);
      err = 0;
    }

    save_errno = errno;
    safe_close(fd);
    errno = save_errno;
  }

  return err;
}


static const struct option long_option[] = {
  { "card", 1, NULL, 'c' },
  { "directory", 1, NULL, 'd' },
  { "help", 0, NULL, 'h' },
  { "version", 0, NULL, 'v' },
  { NULL, 0, NULL, '\0' }
};

static const char option[] = "c:d:hv";

int
main(int argc, char *argv[])
{
  char devicename[32];
  int ret, err;
  snd_hwdep_t *handle;

  const char *directory = default_dir;
  int card = 0;

  int oindex;
  int c;

  while ( (c = getopt_long(argc, argv, option, long_option, &oindex)) != EOF )
  {
    switch(c)
    {
    case 'c':
      card = snd_card_get_index(optarg);
      if (card < 0 || card > 31) {
        fprintf(stderr, "Wrong -c argument '%s'\n", optarg);
        return EXIT_FAILURE;
      }
      break;

    case 'd':
      directory = optarg;
      break;

    case 'h':
      show_usage();
      return EXIT_SUCCESS;

    case 'v':
      show_version();
      return EXIT_SUCCESS;

    default:
      return EXIT_FAILURE;
    } /* switch */
  } /* while */

  ret = EXIT_FAILURE;
  snprintf(devicename, sizeof(devicename), "hw:%i,0", card);
  err = snd_hwdep_open(&handle, devicename, O_WRONLY);
  if (err < 0)
  {
    fprintf(stderr, "Error opening %s: %s\n", devicename, snd_strerror(err)); 
  }
  else
  {
    char filename[FILENAME_MAX];
    size_t len;

    struct sscape_bootblock  boot;
    struct sscape_microcode  microcode;

    microcode.code = _microcode;
    if ((len = get_directory(directory, filename, sizeof(filename))) == 0)
    {
      fprintf(stderr, "Invalid directory - pathname too long\n");
    }
    else if (get_bootfile(scope, filename + len, sizeof(filename) - len) == 0)
    {
      fprintf(stderr, "Invalid filename - full pathname too long\n");
    }
    else if (load_bootblock(filename, &boot) < 0)
    {
      fprintf(stderr, "Failed to load file [%s]: %s\n",
                      filename, strerror(errno));
    }
    else if (snd_hwdep_ioctl(handle, SND_SSCAPE_LOAD_BOOTB, &boot) < 0)
    {
      fprintf(stderr, "IOCTL error: %s\n", strerror(errno));
    }
    else if (get_mcodefile(boot.version & 0x0f,
                           filename + len, sizeof(filename) - len) == 0)
    {
      fprintf(stderr, "Invalid filename - full pathname too long\n"); 
    }
    else if (load_microcode(filename, &microcode) < 0)
    {
      fprintf(stderr, "Failed to load microcode [%s]\n", filename);
    }
    else if (snd_hwdep_ioctl(handle, SND_SSCAPE_LOAD_MCODE, &microcode) < 0)
    {
      fprintf(stderr, "IOCTL error: %s\n", strerror(errno));
    }
    else
    {
      printf("Microcode loaded.\n");
      ret = EXIT_SUCCESS;
    }
    snd_hwdep_close(handle);
  }

  return ret;
}

