// -----------------------------------------------------------------------------
//
//  Copyright (C) 2006-2022 Fons Adriaensen <fons@linuxaudio.org>
//
//  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, see <http://www.gnu.org/licenses/>.
//
// -----------------------------------------------------------------------------


#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <stdio.h>
#include <math.h>
#include "global.h"
#include "audiofile.h"
#include "abprocess.h"


enum
{
     HELP, STIN, HPF,
     ENDF, INVX, INVY, INVZ,
     SN3D, N3D, FUMA,
     ACN, WXYZ,
     CAF, WAV, AMB, AIFF, FLAC,
     BIT16, BIT24, FLOAT,
     REC, TRI, SHA 
};
enum { BUFFSIZE = 256 };


static float hpfr = 0;
static bool  stin = false;
static bool  wxyz = false;
static bool  endf = false;
static int   invb = 0;
static int   norm = FM_SN3D;
static int   type = Audiofile::TYPE_WAV;
static int   form = Audiofile::FORM_24BIT;
static int   dith = Audiofile::DITHER_NONE;


static void help (void)
{
    fprintf (stderr, "\ntetrafile %s\n", VERSION);
    fprintf (stderr, "(C) 2006-2022 Fons Adriaensen  <fons@linuxaudio.org>\n");
    fprintf (stderr, "Usage: tetrafile <options> <config file> <input file(s)> <output file>.\n\n");
    fprintf (stderr, "Options:\n");
    fprintf (stderr, "  Display this text:     --help\n");
    fprintf (stderr, "  Stereo input files:    --stin\n");
    fprintf (stderr, "  Highpass filter:       --hpf <frequency>\n");
    fprintf (stderr, "  Endfire mode:          --endf\n");
    fprintf (stderr, "  B form inversion:      --invx, --invy, --invz\n");
    fprintf (stderr, "  B form gains:          --sn3d, --n3d, --fuma\n");
    fprintf (stderr, "  B form channel order:  --acn, --wxyz\n");
    fprintf (stderr, "  Output file type:      --caf, --wav, -aiff, --flac, --amb\n");
    fprintf (stderr, "  Output sample format:  --16bit, --24bit, --float\n");
    fprintf (stderr, "  Dither type (16 bit):  --rec, --tri, --sha\n");
    fprintf (stderr, "\nThe default options are --sn3d, --acn --wav, --24bit.\n\n");
    exit (1);
}


static struct option options [] = 
{
    { "help",  0, 0, HELP  },
    { "stin",  0, 0, STIN  },
    { "endf",  0, 0, ENDF  },
    { "invx",  0, 0, INVX  },
    { "invy",  0, 0, INVY  },
    { "invz",  0, 0, INVZ  },
    { "sn3d",  0, 0, SN3D  },
    { "n3d",   0, 0, N3D   },
    { "fuma",  0, 0, FUMA  },
    { "acn",   0, 0, ACN   },
    { "wxyz",  0, 0, WXYZ  },
    { "caf",   0, 0, CAF   },
    { "wav",   0, 0, WAV   },
    { "aiff",  0, 0, AIFF  },
    { "flac",  0, 0, FLAC  },
    { "amb",   0, 0, AMB   },
    { "hpf",   1, 0, HPF   },
    { "16bit", 0, 0, BIT16 },
    { "24bit", 0, 0, BIT24 },
    { "float", 0, 0, FLOAT },
    { "rec",   0, 0, REC   },
    { "tri",   0, 0, TRI   },
    { "sha",   0, 0, SHA   },
    { 0, 0, 0, 0 }
};


static void procoptions (int ac, char *av [])
{
    int k;

    while ((k = getopt_long (ac, av, "", options, 0)) != -1)
    {
	switch (k)
	{
        case '?':
	case HELP:
	    help ();
	    break;
	case STIN:
	    stin = true;
	    break;
	case HPF:
	    if ((sscanf (optarg, "%f", &hpfr) != 1) || (hpfr < 10) || (hpfr > 160))
	    {
		fprintf (stderr, "Illegal value for --hpf option: '%s'.\n", optarg);
		exit (1);
	    }
	    break;
	case ENDF:
	    endf = true;
	    break;
	case INVX:
	    invb |= 2;
	    break;
	case INVY:
	    invb |= 4;
	    break;
	case INVZ:
	    invb |= 8;
	    break;
	case SN3D:
	    norm = FM_SN3D;
	    break;
	case N3D:
	    norm = FM_N3D;
	    break;
	case FUMA:
	    norm = FM_FUMA;
	    break;
	case ACN:
	    wxyz = false;
	    break;
	case WXYZ:
	    wxyz = true;
	    break;
	case CAF:
	    type = Audiofile::TYPE_CAF;
	    break;
	case WAV:
	    type = Audiofile::TYPE_WAV;
	    break;
	case AIFF:
	    type = Audiofile::TYPE_AIFF;
	    break;
	case FLAC:
	    type = Audiofile::TYPE_FLAC;
	    break;
	case AMB:
	    type = Audiofile::TYPE_AMB;
	    break;
	case BIT16:
	    form = Audiofile::FORM_16BIT;
	    break;
	case BIT24:
	    form = Audiofile::FORM_24BIT;
	    break;
	case FLOAT:
	    form = Audiofile::FORM_FLOAT;
	    break;
	case REC:
	    dith = Audiofile::DITHER_RECT;
	    break;
	case TRI:
	    dith = Audiofile::DITHER_TRIA;
	    break;
	case SHA:
	    dith = Audiofile::DITHER_LIPS;
	    break;
 	}
    }
}


int main (int ac, char *av [])
{
    Audiofile     Ainp1;
    Audiofile     Ainp2;
    Audiofile     Aout;
    ABprocess     abproc;
    ABconfig      config;
    unsigned int  i, k, rate;
    float         *buff, *inp [4], *out [4];
    float         *p;
    int           nargs, nchan;

    
    procoptions (ac, av);
    nargs = stin ? 4 : 3;
    nchan = stin ? 2 : 4;

    if (ac - optind < nargs)
    {
        fprintf (stderr, "Missing arguments, try --help.\n");
	return 1;
    }
    if (ac - optind > nargs )
    {
        fprintf (stderr, "Too many arguments, try --help.\n");
	return 1;
    }

    if (config.load (av [optind]))
    {
	fprintf (stderr, "Can't open config file '%s'.\n", av [optind]);
	return 1;
    }

    optind++;
    if (Ainp1.open_read (av [optind]))
    {
	fprintf (stderr, "Can't open input file '%s'.\n", av [optind]);
	return 1;
    }
    if (Ainp1.chan () != (int) nchan)
    {
	fprintf (stderr, "Input file '%s' should have %d channels.\n", av [optind], nchan);
	Ainp1.close ();
	return 1;
    }
    rate = Ainp1.rate ();

    if (stin)
    {
        optind++;
        if (Ainp2.open_read (av [optind]))
        {
	    fprintf (stderr, "Can't open input file '%s'.\n", av [optind]);
	    Ainp1.close ();
	    return 1;
        }
        if (Ainp2.chan () != 2)
        {
	    fprintf (stderr, "Input file '%s' should have 2 channels.\n", av [optind]);
	    Ainp1.close ();
	    Ainp2.close ();
	    return 1;
        }
	if (Ainp1.size () != Ainp2.size ())
	{
	    fprintf (stderr, "Sizes of input files don't match.\n");
	    Ainp1.close ();
	    Ainp2.close ();
	    return 1;
        }
	if (Ainp1.rate () != Ainp2.rate ())
	{
	    fprintf (stderr, "Sample rates of input files don't match.\n");
	    Ainp1.close ();
	    Ainp2.close ();
	    return 1;
        }
    }

    optind++;
    if (Aout.open_write (av [optind], type, form, rate, 4))
    {
	fprintf (stderr, "Can't open output file '%s'.\n", av [optind]);
	Ainp1.close ();
	Ainp2.close ();
	return 1;
    }
    if (dith != Audiofile::DITHER_NONE) 
    {
	Aout.set_dither (dith);
    }

    abproc.init (rate, BUFFSIZE, BUFFSIZE);
    abproc.set_lffilt (&config);
    abproc.set_matrix (&config);
    abproc.set_convol (&config);
    abproc.set_hffilt (&config); 
    abproc.set_hpfil ((hpfr < 1) ? 1 : hpfr);
    abproc.set_invb (invb);
    abproc.set_endf (endf);
    abproc.set_norm (norm);

    buff = new float [4 * BUFFSIZE];
    for (i = 0; i < 4; i++)
    {
	inp [i] = new float [BUFFSIZE];
	out [i] = new float [BUFFSIZE];
    }

    while (true)
    {
        k = Ainp1.read (buff, BUFFSIZE);
        if (k)
        {
 	    if (stin)
	    {
                for (i = 0, p = buff; i < k; i++, p += 2)
                {
		    inp [0][i] = p [0];
		    inp [1][i] = p [1];
		}
   	        Ainp2.read (buff, BUFFSIZE);
                for (i = 0, p = buff; i < k; i++, p += 2)
                {
		    inp [2][i] = p [0];
		    inp [3][i] = p [1];
		}
	    }
            else
	    {
                for (i = 0, p = buff; i < k; i++, p += 4)
                {
		    inp [0][i] = p [0];
		    inp [1][i] = p [1];
		    inp [2][i] = p [2];
		    inp [3][i] = p [3];
		}
	    }

	    abproc.process (k, inp, out); 
	    if (wxyz)
            {		
		for (i = 0, p = buff; i < k; i++, p += 4)
		{
		    p [0] = out [0][i];
		    p [1] = out [1][i];
		    p [2] = out [2][i];
		    p [3] = out [3][i];
		}
	    }
	    else
            {		
		for (i = 0, p = buff; i < k; i++, p += 4)
		{
		    p [0] = out [0][i];
		    p [1] = out [2][i];
		    p [2] = out [3][i];
		    p [3] = out [1][i];
		}
	    }
            Aout.write (buff, k);
	}
        else break;
    }

    Aout.close ();
    Ainp1.close ();
    Ainp2.close ();
    delete[] buff;
    for (i = 0; i < 4; i++)
    {
	delete[] inp [i];
	delete[] out [i];
    }

    return 0;
}
