/* emacs edit mode for this file is -*- C++ -*- */

#include <config.h>

#include "assert.h"

#include "cf_defs.h"
#include "int_pp.h"
#include "canonicalform.h"
#include "cf_factory.h"
#include "imm.h"

mpz_t InternalPrimePower::primepow;
mpz_t InternalPrimePower::primepowhalf;
int InternalPrimePower::prime;
int InternalPrimePower::exp;
int InternalPrimePower::initialized = InternalPrimePower::initialize();


InternalPrimePower::InternalPrimePower()
{
    mpz_init( thempi );
}

InternalPrimePower::InternalPrimePower( const int i )
{
    mpz_init_set_si( thempi, i );
    if ( mpz_cmp_si( thempi, 0 ) < 0 ) {
        mpz_neg( thempi, thempi );
        mpz_mod( thempi, thempi, primepow );
        mpz_sub( thempi, primepow, thempi );
    }
    else
        mpz_mod( thempi, thempi, primepow );
}

InternalPrimePower::InternalPrimePower( const mpz_ptr mpi) { thempi[0]=*mpi;}

InternalPrimePower::InternalPrimePower( const char * str, const int base )
{
    mpz_init_set_str( thempi, str, base );
    if ( mpz_cmp_si( thempi, 0 ) < 0 ) {
        mpz_neg( thempi, thempi );
        mpz_mod( thempi, thempi, primepow );
        mpz_sub( thempi, primepow, thempi );
    }
    else
        mpz_mod( thempi, thempi, primepow );
}

InternalPrimePower::~InternalPrimePower()
{
    mpz_clear( thempi );
}

InternalCF* InternalPrimePower::deepCopyObject() const
{
    mpz_t dummy;
    mpz_init_set( dummy, thempi );
    return new InternalPrimePower( dummy );
}

InternalCF * InternalPrimePower::normalize_myself()
{
    ASSERT( getRefCount() == 1, "illegal operation" );
    if ( mpz_cmp_si( thempi, 0 ) < 0 ) {
        mpz_neg( thempi, thempi );
        mpz_mod( thempi, thempi, primepow );
        mpz_sub( thempi, primepow, thempi );
    }
    else
        mpz_mod( thempi, thempi, primepow );
    return this;
}

int InternalPrimePower::initialize()
{
    mpz_init_set_si( primepow, 3 );
    mpz_init_set_si( primepowhalf, 1 );
    prime = 3;
    exp = 1;
    return 1;
}

void
InternalPrimePower::setPrimePower( int p, int k )
{
    ASSERT( p > 1 && k > 0, "illegal prime power" );
    if ( p != prime || k != exp ) {
        mpz_set_si( primepow, p );
        mpz_pow_ui( primepow, primepow, (unsigned int)k );
        mpz_fdiv_q_ui( primepowhalf, primepow, 2 );
        prime = p;
        exp = k;
    }
}

int
InternalPrimePower::getp()
{
    return prime;
}

int
InternalPrimePower::getk()
{
    return exp;
}

#ifndef NOSTREAMIO
void InternalPrimePower::print( OSTREAM & os, char * c )
{
    if ( *c == '*' && mpz_cmp_si( thempi, 1 ) == 0 )
        os << c+1;
    else if ( *c == '*' && mpz_cmp_si( thempi, -1 ) == 0 )
        os << '-' << c+1;
    else {
        char * str = new char[mpz_sizeinbase( thempi, 10 ) + 2];
        str = mpz_get_str( str, 10, thempi );
        os << str << c;
        delete [] str;
    }
}
#endif /* NOSTREAMIO */

//{{{ bool InternalPrimePower::isOne, isZero () const
// docu: see CanonicalForm::isOne(), CanonicalForm::isZero()
bool
InternalPrimePower::isOne () const
{
    return mpz_cmp_ui( thempi, 1 ) == 0;
}

bool
InternalPrimePower::isZero () const
{
    return mpz_sgn( thempi ) == 0;
}
//}}}

bool InternalPrimePower::is_imm() const
{
    return false;
}

InternalCF* InternalPrimePower::genZero()
{
    if ( isZero() )
        return copyObject();
    else
        return new InternalPrimePower();
}

InternalCF* InternalPrimePower::genOne()
{
    if ( isOne() )
        return copyObject();
    else
        return new InternalPrimePower();
}

//{{{ InternalCF * InternalPrimePower::neg ()
// docu: see CanonicalForm::operator -()
InternalCF *
InternalPrimePower::neg ()
{
    if ( getRefCount() > 1 ) {
        decRefCount();
        mpz_t dummy;
        mpz_init( dummy );
        mpz_sub( dummy, primepow, thempi );
        return new InternalPrimePower( dummy );
    } else {
        mpz_sub( thempi, primepow, thempi );
        return this;
    }
}
//}}}


InternalCF* InternalPrimePower::addsame( InternalCF * c )
{
    if ( getRefCount() > 1 ) {
        decRefCount();
        mpz_t dummy;
        mpz_init( dummy );
        mpz_add( dummy, thempi, MPI( c ) );
        if ( mpz_cmp( dummy, primepow ) >= 0 )
            mpz_sub( dummy, dummy, primepow );
        return new InternalPrimePower( dummy );
    }
    else {
        mpz_add( thempi, thempi, MPI( c ) );
        if ( mpz_cmp( thempi, primepow ) >= 0 )
            mpz_sub( thempi, thempi, primepow );
        return this;
    }
}

InternalCF* InternalPrimePower::subsame( InternalCF * c )
{
    if ( getRefCount() > 1 ) {
        decRefCount();
        mpz_t dummy;
        mpz_init( dummy );
        mpz_sub( dummy, thempi, MPI( c ) );
        if ( mpz_cmp_si( dummy, 0 ) < 0 )
            mpz_add( dummy, dummy, primepow );
        return new InternalPrimePower( dummy );
    }
    else {
        mpz_sub( thempi, thempi, MPI( c ) );
        if ( mpz_cmp_si( thempi, 0 ) < 0 )
            mpz_add( thempi, thempi, primepow );
        return this;
    }
}

InternalCF* InternalPrimePower::mulsame( InternalCF * c )
{
    if ( getRefCount() > 1 ) {
        decRefCount();
        mpz_t dummy;
        mpz_init( dummy );
        mpz_mul( dummy, thempi, MPI( c ) );
        mpz_mod( dummy, dummy, primepow );
        return new InternalPrimePower( dummy );
    }
    else {
        mpz_mul( thempi, thempi, MPI( c ) );
        mpz_mod( thempi, thempi, primepow );
        return this;
    }
}

InternalCF* InternalPrimePower::dividesame( InternalCF * c )
{
    return divsame( c );
}

InternalCF* InternalPrimePower::divsame( InternalCF * c )
{
    if ( c == this ) {
        if ( deleteObject() ) delete this;
        return CFFactory::basic( 1L );
    }
    if ( getRefCount() > 1 ) {
        decRefCount();
        mpz_t dummy, a, b;
        mpz_init( dummy ); mpz_init( a ); mpz_init( b );
        mpz_gcdext( dummy, a, b, primepow, MPI( c ) );
        ASSERT( mpz_cmp_si( dummy, 1 ) == 0, "illegal inversion" );
        mpz_clear( dummy ); mpz_clear( a );
        if ( mpz_cmp_si( b, 0 ) < 0 )
            mpz_add( b, b, primepow );
        mpz_mul( b, b, thempi );
        mpz_mod( b, b, primepow );
        return new InternalPrimePower( b );
    }
    else {
        mpz_t dummy, a, b;
        mpz_init( dummy ); mpz_init( a ); mpz_init( b );
        mpz_gcdext( dummy, a, b, primepow, MPI( c ) );
        ASSERT( mpz_cmp_si( dummy, 1 ) == 0, "illegal inversion" );
        if ( mpz_cmp_si( b, 0 ) < 0 )
            mpz_add( b, b, primepow );
        mpz_mul( thempi, b, thempi );
        mpz_mod( thempi, thempi, primepow );
        mpz_clear( dummy ); mpz_clear( a ); mpz_clear( b );
        return this;
    }
}

InternalCF *
InternalPrimePower::modulosame ( InternalCF * )
{
    if ( deleteObject() ) delete this;
    return CFFactory::basic( 0L );
}

InternalCF *
InternalPrimePower::modsame ( InternalCF * )
{
    if ( deleteObject() ) delete this;
    return CFFactory::basic( 0L );
}

void
InternalPrimePower::divremsame ( InternalCF * c, InternalCF * & quot, InternalCF * & rem )
{
    if ( c == this ) {
        quot = CFFactory::basic( 1L );
        rem = CFFactory::basic( 0L );
    }
    else {
        mpz_t dummy, a, b;
        mpz_init( dummy ); mpz_init( a ); mpz_init( b );
        mpz_gcdext( dummy, a, b, primepow, MPI( c ) );
        ASSERT( mpz_cmp_si( dummy, 1 ) == 0, "illegal inversion" );
        mpz_clear( dummy ); mpz_clear( a );
        if ( mpz_cmp_si( b, 0 ) < 0 )
            mpz_add( b, b, primepow );
        mpz_mul( b, b, thempi );
        mpz_mod( b, b, primepow );
        quot = new InternalPrimePower( b );
        rem = CFFactory::basic( 0L );
    }
}

bool
InternalPrimePower::divremsamet ( InternalCF * c, InternalCF * & quot, InternalCF * & rem )
{
    divremsame( c, quot, rem );
    return true;
}

//{{{ int InternalPrimePower::comparesame, comparecoeff ( InternalCF * c )
// docu: see CanonicalForm::operator <(), CanonicalForm::operator ==()
int
InternalPrimePower::comparesame ( InternalCF * c )
{
    ASSERT( ! ::is_imm( c ) && c->levelcoeff() == PrimePowerDomain, "incompatible base coefficients" );
    return mpz_cmp( thempi, MPI( c ) );
}

int
InternalPrimePower::comparecoeff ( InternalCF * )
{
    ASSERT1( 0, "comparecoeff() not implemented for class %s", this->classname() );
    return 0;
}
//}}}

InternalCF *
InternalPrimePower::addcoeff ( InternalCF * )
{
    ASSERT( 0, "this function should never be called" );
    return this;
}

InternalCF *
InternalPrimePower::subcoeff ( InternalCF *, bool )
{
    ASSERT( 0, "this function should never be called" );
    return this;
}

InternalCF *
InternalPrimePower::mulcoeff ( InternalCF * )
{
    ASSERT( 0, "this function should never be called" );
    return this;
}

InternalCF *
InternalPrimePower::dividecoeff ( InternalCF *, bool )
{
    ASSERT( 0, "this function should never be called" );
    return this;
}

InternalCF *
InternalPrimePower::divcoeff ( InternalCF *, bool )
{
    ASSERT( 0, "this function should never be called" );
    return this;
}

InternalCF *
InternalPrimePower::modcoeff ( InternalCF *, bool )
{
    ASSERT( 0, "this function should never be called" );
    return this;
}

InternalCF *
InternalPrimePower::modulocoeff ( InternalCF *, bool )
{
    ASSERT( 0, "this function should never be called" );
    return this;
}

void
InternalPrimePower::divremcoeff ( InternalCF *, InternalCF * &, InternalCF * &, bool )
{
    ASSERT( 0, "this function should never be called" );
}

bool
InternalPrimePower::divremcoefft ( InternalCF *, InternalCF * &, InternalCF * &, bool )
{
    ASSERT( 0, "this function should never be called" );
    return true;
}

long
InternalPrimePower::intval () const
{
  return mpz_get_si( thempi );
}

int
InternalPrimePower::intmod( int p ) const
{
  return (int)mpz_fdiv_ui( thempi, (unsigned long)p );
}

//{{{ int InternalPrimePower::sign () const
// docu: see CanonicalForm::sign()
int
InternalPrimePower::sign () const
{
    return mpz_sgn( thempi );
}
//}}}
