/*
This source file is part of Scol
For the latest info, see http://www.scolring.org

Copyright (c) 2010 Stephane Bisaro, aka Iri <iri@irizone.net>

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt

For others informations, please contact us from http://www.scolring.org/
*/

#include "statistic.h"


double statistic_calc_factorielle (int n)
{
   double r = 1;
   int i = 1;

   for (i = 1; i <= n; i++)
   {
      r *= i;
   }

   return r;
}

/* n! / (k!(n-k)! */
double statistic_calc_cnk (int n, int k)
{
    double r, r1, r2;

    r1 = statistic_calc_factorielle (n);
    r2 = statistic_calc_factorielle (k) * statistic_calc_factorielle (n - k);
    r = r1 / r2;
    return r;
}

/* ((n-k-1)!) / (((k!)((n-1)!)) */
double statistic_calc_tnk (int n, int k)
{
    double r, r1, r2;

    r1 = statistic_calc_factorielle (n + k - 1);
    r2 = statistic_calc_factorielle (k) * statistic_calc_factorielle (n - 1);
    r = r1 / r2;
    return r;
}

/* Akn = n!/(n-k)! */
double statistic_calc_akn (int n, int k)
{
    double r, r1, r2;

    r1 = statistic_calc_factorielle (n);
    r2 = statistic_calc_factorielle (n - k);
    r = r1 / r2;
    return r;
}

double statistic_calc_nk (int n, int k)
{
    double r;

    r = pow ((double) n, (double) k);
    return r;
}




/**
 * \brief : compare two string number
 *
 * The strings must be any numbers. If the first is bigger than the second, this
 * function returns 1;
 * If the second is bigger than the first, this function returns -1;
 * If the first is equal at the second, this function returns 0
 *
 * Stack :
 * stage 0 : S : the second number
 * stage 1 : S : the first number
 * return : I : 0, 1, -1. Nil if error
 */
int SCOLstatisticCmpStrings (mmachine m)
{
    int ms1, ms2;
    int len, i, r = 0;
    char *s1, *s2;

    MMechostr (MSKDEBUG, "SCOLstatisticCmpStrings : entering\n");

    ms2 = MTOP (MMpull (m));
    ms1 = MTOP (MMpull (m));

    if ((ms1 == NIL) || (ms2 == NIL))
    {
        MMechostr (0, "SCOLstatisticCmpStrings error : string is nil\n");
        MMpush (m, NIL);
        return 0;
    }

    s1 = MMstartstr (m, ms1);
    s2 = MMstartstr (m, ms2);

    if ((!(nisdigit (s1, strlen (s1)))) || (!(nisdigit (s1, strlen (s2)))))
    {
        MMechostr (0, "SCOLstatisticCmpStrings error : string are invalid (not only digit !)\n");
        MMpush (m, NIL);
        return 0;
    }

    s1 = removeZeroBefore (s1);
    s2 = removeZeroBefore (s2);

    if  (strlen (s1) > strlen (s2))
        r = 1;
    else if (strlen (s1) < strlen (s2))
        r = -1;
    else
    {
        len = strlen (s1);
        for (i = 0; i < len; i++)
        {
            if (s1[i] > s2[i])
            {
                r = 1;
                break;
            }
            else if (s1[i] < s2[i])
            {
                r = -1;
                break;
            }
        }
    }

    MMpush (m, ITOM (r));
    return 0;
}

/**
 * \brief Get if a string is a negative or positive number
 *
 * Example : "21365" if positive, "-854" is negative, "-54A5" is invalid (A)
 * Stack :
 * Stage 0 : S : any string
 * Return : 1 if negative, 0 if positive, nil if error
 */
int SCOLstatisticIsNegativeString (mmachine m)
{
    int ms;
    char *s;

    MMechostr (MSKDEBUG, "SCOLstatisticIsNegativeString : entering\n");

    ms = MTOP (MMpull (m));

    if (ms == NIL)
    {
        MMechostr (MSKDEBUG, "SCOLstatisticIsNegativeString error : string is nil\n");
        MMpush (m, NIL);
        return 0;
    }

    s = MMstartstr (m, ms);

    if (!(nisdigit (s+1, strlen (s+1))))
    {
        MMechostr (MSKDEBUG, "SCOLstatisticIsNegativeString error : string is not valid (only digits)\n");
        MMpush (m, NIL);
        return 0;
    }

    if (s[0] == '-')
        MMpush (m, ITOM (1));
    else if (isdigit (s[0]))
        MMpush (m, ITOM (0));
    else
    {
        MMechostr (MSKDEBUG, "SCOLstatisticIsNegativeString error : string is not valid (only digits)\n");
        MMpush (m, NIL);
    }
    return 0;
}


/**
 * \brief Add two string number (example : "243" "39901" = "40144")
 *
 * Stack :
 * Stage 0 : S : string 2 (positive only)
 * Stage 1 : S : string 1 (positive only)
 * Return : S : the result
 */
int SCOLstatisticAddStrings (mmachine m)
{
    int ms1, ms2;
    int len1, len2, len, r, i;
    char *s1, *s2, *s;

    MMechostr (MSKDEBUG, "SCOLstatisticAddStrings : entering\n");

    ms2 = MTOP (MMpull (m));
    ms1 = MTOP (MMpull (m));

    if ((ms1 == NIL) || (ms2 == NIL))
    {
        MMechostr (0, "SCOLstatisticAddStrings error : string is nil\n");
        MMpush (m, NIL);
        return 0;
    }

    s1 = MMstartstr (m, ms1);
    s2 = MMstartstr (m, ms2);

    if ((!(nisdigit (s1, strlen (s1)))) || (!(nisdigit (s1, strlen (s2)))))
    {
        MMechostr (0, "SCOLstatisticAddStrings error : string are invalid (not only digit !)\n");
        MMpush (m, NIL);
        return 0;
    }

    /* remove first zeros if needed */
    s1 = removeZeroBefore (s1);
    s2 = removeZeroBefore (s2);

    len1 = strlen (s1);
    len2 = strlen (s2);
    len = len1 + 1;

    /* Each string has the same length : if needed, zeros are added to first */
    if (len1 > len2)
    {
        len = len1 - len2;
        s = (char*) malloc (sizeof (char) * len);
        memset (s, '0', len);
        s[len] = '\0';
        s2 = addPrefix (s2, s);
        free(s);
        len = len1;
    }
    else if (len1 < len2)
    {
        len = len2 - len1;
        s = (char*) malloc (sizeof (char) * len);
        memset (s, '0', len);
        s[len] = '\0';
        s1 = addPrefix (s1, s);
        free(s);
        len = len2;
    }
    else
    {
        s1 = addPrefixChar (s1, '0');
        s2 = addPrefixChar (s2, '0');
    }

    s = (char *) malloc (sizeof (char) * len);
    memset (s, '0', len);
    s[len] = '\0';

    /* addition : indice by indice, begining by the end (except the NULL char terminated) */
    for (i = 1; i <= len; i++)
    {
        r = ((s1[len-i]-'0') + (s2[len-i]-'0') + (s[len-i]-'0'));

        if (r < 10)
            s[len-i] = r+'0';
        else
        {
            s[len-i] = (r-10)+'0';
            s[len-i-1] = '1';
        }
    }
    s[len] = '\0';

    Mpushstrbloc (m, removeZeroBefore (s));
    free (s);
    return 0;
}

/**
 * \brief Substract two string number (example : "782" "1626" = "-844")
 *
 * Stack :
 * Stage 0 : S : string 2 (positive only)
 * Stage 1 : S : string 1 (positive only)
 * Return : S : the result
 */
int SCOLstatisticSubStrings (mmachine m)
{
    int ms1, ms2;
    int len1, len2, len, i;
    int* r;
    double k = 0.0;
    char *s1, *s2, *s;

    MMechostr (MSKDEBUG, "SCOLstatisticSubStrings : entering\n");

    ms2 = MTOP (MMpull (m));
    ms1 = MTOP (MMpull (m));

    if ((ms1 == NIL) || (ms2 == NIL))
    {
        MMechostr (0, "SCOLstatisticSubStrings error : string is nil\n");
        MMpush (m, NIL);
        return 0;
    }

    s1 = MMstartstr (m, ms1);
    s2 = MMstartstr (m, ms2);

    if ((!(nisdigit (s1, strlen (s1)))) || (!(nisdigit (s1, strlen (s2)))))
    {
        MMechostr (0, "SCOLstatisticSubStrings error : string are invalid (not only digit !)\n");
        MMpush (m, NIL);
        return 0;
    }

    /* remove first zeros if needed */
    s1 = removeZeroBefore (s1);
    s2 = removeZeroBefore (s2);

    len1 = strlen (s1);
    len2 = strlen (s2);
    len = len1;

    /* Each string has the same length : if needed, zeros are added to first */
    if (len1 > len2)
    {
        len = len1 - len2;
        s = (char *) malloc (sizeof (char) * len);
        memset (s, '0', len);
        s[len] = '\0';
        s2 = addPrefix (s2, s);
        free (s);
        len = len1;
    }
    else if (len1 < len2)
    {
        len = len2 - len1;
        s = (char *) malloc (sizeof (char) * len);
        memset (s, '0', len);
        s[len] = '\0';
        s1 = addPrefix (s1, s);
        free (s);
        len = len2;
    }

    r = (int*) malloc (sizeof (int) * len);
    for (i = 0; i <= len; i++)
        r[i] = 0;

    /* begin calc */
    for(i = 0; i < len; i++)
        r[i] = (s1[i]-'0') - (s2[i]-'0');

    for (i = 1; i <= len; i++)
        k += r[len-i] * pow (10, i-1);
    /* end calc */

    len += 2; /* sign '-' if needed */
    s = (char *) malloc (sizeof (char) * len);
    snprintf (s, len, "%.0f", k);
    s[len] = '\0';

    Mpushstrbloc (m, s);
    free (s);
    free (r);
    return 0;
}




/**
 * \brief : factorielle
 *
 * n!
 *
 * Stack :
 * stage 0 : I : any integer between 1 and 170 (171 rejected, 170! = ~7.10e308)
 * return : S : the result (string)
 */
int SCOLfactorielle (mmachine m)
{
    int mn;
    double r;
    char *s = NULL;

    MMechostr (MSKDEBUG, "SCOLfactorielle : entering\n");

    mn = MTOI (MMpull (m));

    if (mn < 0)
    {
        MMechostr (MSKDEBUG, "SCOLfactorielle error : the number must be positive\n");
        MMpush (m, NIL);
        return 0;
    }
    else if (mn > 170)
    {
        MMechostr (MSKDEBUG, "SCOLfactorielle error : the number is too big\n");
        MMpush (m, NIL);
        return 0;
    }

    s = (char *) malloc (sizeof (char) * 310);
    r = statistic_calc_factorielle (mn);
    sprintf(s, "%.0f", r);
    Mpushstrbloc (m, s);
    free (s);
    return 0;
}

/**
 * \brief : factorielle
 *
 * n!
 *
 * Stack :
 * stage 0 : I : any integer between 1 and 34 (35 rejected, 30! = ~3.10e38)
 * return : F : the result
 */
int SCOLfactorielleF (mmachine m)
{
    int mn;
    float r, f;

    MMechostr (MSKDEBUG, "SCOLfactorielleF : entering\n");

    mn = MTOI (MMpull (m));

    if (mn < 0)
    {
        MMechostr (MSKDEBUG, "SCOLfactorielleF error : the number must be positive\n");
        MMpush (m, NIL);
        return 0;
    }
    else if (mn > 34)
    {
        MMechostr (MSKDEBUG, "SCOLfactorielleF error : the number is too big\n");
        MMpush (m, NIL);
        return 0;
    }

    r = (float) statistic_calc_factorielle (mn);
    FSET (f, r);
    MMpush (m, f);
    return 0;
}



/**
 * \brief Calculate : nombre de façons de choisir k éléments parmi un ensemble de n
 *
 * |          |
 * |    n!    |
 * | ________ |
 * | k!(n-k)! |
 * |          |
 *
 * Stack :
 * Stage 0 : I : k
 * Stage 1 : I : n
 * Return : S : the result (string)
 */
int SCOLstatisticCombinaisonSansRepetition (mmachine m)
{
    int mn, mk;
    double r;
    char *s = NULL;

    MMechostr (MSKDEBUG, "SCOLstatisticCombinaisonSansRepetition : entering\n");

    mk = MTOI (MMpull (m));
    mn = MTOI (MMpull (m));

    if ((mn < 0) || (mk < 0))
    {
        MMechostr (MSKDEBUG, "SCOLstatisticCombinaisonSansRepetition error : the number must be positive\n");
        MMpush (m, NIL);
        return 0;
    }
    else if (mn > 170)
    {
        MMechostr (MSKDEBUG, "SCOLstatisticCombinaisonSansRepetition error : the number is too big\n");
        MMpush (m, NIL);
        return 0;
    }
    else if (mk > mn)
    {
        MMechostr (MSKDEBUG, "SCOLstatisticCombinaisonSansRepetition error : k > n !\n");
        MMpush (m, NIL);
        return 0;
    }

    r = statistic_calc_cnk (mn, mk);

    s = (char *) malloc (sizeof (char) * 310);
    sprintf(s, "%.0f", r);
    Mpushstrbloc (m, s);
    free (s);
    return 0;
}

/**
 * \brief Calculate : nombre de façons de choisir k éléments parmi un ensemble de n
 *
 * |          |
 * | (n+k-1)! |
 * | ________ |
 * | k!(n-1)! |
 * |          |
 *
 * Stack :
 * Stage 0 : I : k
 * Stage 1 : I : n
 * Return : S : the result (string)
 */
int SCOLstatisticCombinaisonAvecRepetition (mmachine m)
{
    int mn, mk;
    double r;
    char *s = NULL;

    MMechostr (MSKDEBUG, "SCOLstatisticCombinaisonAvecRepetition : entering\n");

    mk = MTOI (MMpull (m));
    mn = MTOI (MMpull (m));

    if ((mn <= 1) || (mk <= 0))
    {
        MMechostr (MSKDEBUG, "SCOLstatisticCombinaisonAvecRepetition error : the number must be positive\n");
        MMpush (m, NIL);
        return 0;
    }
    else if (mn > 170)
    {
        MMechostr (MSKDEBUG, "SCOLstatisticCombinaisonAvecRepetition error : the number is too big\n");
        MMpush (m, NIL);
        return 0;
    }
    else if (mk > mn)
    {
        MMechostr (MSKDEBUG, "SCOLstatisticCombinaisonAvecRepetition error : k > n !\n");
        MMpush (m, NIL);
        return 0;
    }

    r = statistic_calc_tnk (mn, mk);

    s = (char *) malloc (sizeof (char) * 310);
    sprintf(s, "%.0f", r);
    Mpushstrbloc (m, s);
    free (s);
    return 0;
}


/**
 * \brief Arrangement sans répétition : Pour n objets distincts, on en tire k sans doublons mais avec ordre
 *
 * |          |
 * |    n!    |
 * | ________ |
 * |  (n-k)!  |
 * |          |
 *
 * Stack :
 * Stage 0 : I : k
 * Stage 1 : I : n
 * Return : S : the result (string)
 */
int SCOLstatisticArrangementSansReptition (mmachine m)
{
    int mn, mk;
    double r;
    char *s = NULL;

    MMechostr (MSKDEBUG, "SCOLstatisticArrangementSansReptition : entering\n");

    mk = MTOI (MMpull (m));
    mn = MTOI (MMpull (m));

    if ((mn < 0) || (mk < 0))
    {
        MMechostr (MSKDEBUG, "SCOLstatisticArrangementSansReptition error : the number must be positive\n");
        MMpush (m, NIL);
        return 0;
    }
    else if (mn > 170)
    {
        MMechostr (MSKDEBUG, "SCOLstatisticArrangementSansReptition error : the number is too big\n");
        MMpush (m, NIL);
        return 0;
    }
    else if (mk > mn)
    {
        MMechostr (MSKDEBUG, "SCOLstatisticArrangementSansReptition error : k > n !\n");
        MMpush (m, NIL);
        return 0;
    }

    if (mn == mk)
    {
        Mpushstrbloc (m, "1");
        return 0;
    }

    r = statistic_calc_akn (mn, mk);

    s = (char *) malloc (sizeof (char) * 310);
    sprintf(s, "%.0f", r);
    Mpushstrbloc (m, s);
    free (s);
    return 0;
}

/**
 * \brief Choisir k objets parmi n, avec doublons (ou plus) possibles
 *
 *  n^k
 *
 * Stack :
 * Stage 0 : I : k
 * Stage 1 : I : n
 * Return : S : the result (string)
 */
int SCOLstatisticArrangementAvecRepetition (mmachine m)
{
    int mn, mk;
    double r;
    char *s = NULL;

    MMechostr (MSKDEBUG, "SCOLstatisticArrangementAvecRepetition : entering\n");

    mk = MTOI (MMpull (m));
    mn = MTOI (MMpull (m));

    if ((mn < 0) || (mk < 0))
    {
        MMechostr (MSKDEBUG, "SCOLstatisticArrangementAvecRepetition error : the number must be positive\n");
        MMpush (m, NIL);
        return 0;
    }
    else if (mk > mn)
    {
        MMechostr (MSKDEBUG, "SCOLstatisticArrangementAvecRepetition error : k > n !\n");
        MMpush (m, NIL);
        return 0;
    }

    r = statistic_calc_nk (mn, mk);

    s = (char *) malloc (sizeof (char) * 310);
    sprintf(s, "%.0f", r);
    Mpushstrbloc (m, s);
    free (s);
    return 0;
}











/* SCOL API */

/* functions name */
char* science_statistic_name[SCIENCE_STATISTIC_PKG_NB]=
{
    "_scienceStatisticStringsCmp",
    "_scienceStatisticStringsAdd",
    "_scienceStatisticStringsSub",
    "_scienceStatisticStringIsNegative",
    "_scienceStatisticFactorielle", /* n! */
    "_scienceStatisticFactorielleF",
    "_scienceStatisticCombinaisonSansRepetition",   /* n! / (k!(n-k)!) */
    "_scienceStatisticCombinaisonAvecRepetition",   /* (n+k-1)! / (k!(n-1)!) */
    "_scienceStatisticArrangementSansReptition",    /* n! / ((n-k)!) */
    "_scienceStatisticArrangementAvecRepetition"    /* n^k */
};

/* internals functions */
int (*science_statistic_fun[SCIENCE_STATISTIC_PKG_NB])(mmachine m)=
{
    SCOLstatisticCmpStrings,
    SCOLstatisticAddStrings,
    SCOLstatisticSubStrings,
    SCOLstatisticIsNegativeString,
    SCOLfactorielle,
    SCOLfactorielleF,
    SCOLstatisticCombinaisonSansRepetition,
    SCOLstatisticCombinaisonAvecRepetition,
    SCOLstatisticArrangementSansReptition,
    SCOLstatisticArrangementAvecRepetition
};

/* arguments (number) */
int science_statistic_narg[SCIENCE_STATISTIC_PKG_NB]=
{
    2,                  /* _scienceStatisticStringsCmp */
    2,                  /* _scienceStatisticStringsAdd */
    2,                  /* _scienceStatisticStringsSub */
    1,                  /* _scienceStatisticStringIsNegative */
    1,                   /* _scienceStatisticFactorielle */
    1,                   /* _scienceStatisticFactorielleF */
    2,                   /* _scienceStatisticCombinaisonSansRepetition */
    2,                  /* _scienceStatisticCombinaisonAvecRepetition */
    2,                   /* _scienceStatisticArrangementSansReptition */
    2                   /* _scienceStatisticArrangementAvecRepetition */
};

/* functions type */
char* science_statistic_type[SCIENCE_STATISTIC_PKG_NB]=
{
    "fun [S S] I",                   /* _scienceStatisticStringsCmp */
    "fun [S S] S",                  /* _scienceStatisticStringsAdd */
    "fun [S S] S",                  /* _scienceStatisticStringsSub */
    "fun [S] I",                    /* _scienceStatisticStringIsNegative */
    "fun [I] S",                     /* _scienceStatisticFactorielle */
    "fun [I] F",                     /* _scienceStatisticFactorielleF */
    "fun [I I] S",                   /* _scienceStatisticCombinaisonSansRepetition */
    "fun [II] S",                   /* _scienceStatisticCombinaisonAvecRepetition */
    "fun [I I] S",                   /* _scienceStatisticArrangementSansReptition */
    "fun [I I] S"                   /* _scienceStatisticArrangementAvecRepetition */
};

int SCOLstatisticLoadAPI (mmachine m)
{
    int k;

    MMechostr(MSKDEBUG, "SCOLstatisticLoadAPI: entering\n");

    k = PKhardpak (m, "Science_Statistic", SCIENCE_STATISTIC_PKG_NB, science_statistic_name, science_statistic_fun, science_statistic_narg, science_statistic_type);
    return k;
}
