# -*-perl-*-
#
# $Id$
#
# Field abstraction
#
# (c) 2004 Straylight/Edgeware
#

#----- Licensing notice -----------------------------------------------------
#
# This file is part of the Perl interface to Catacomb.
#
# Catacomb/Perl 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.
# 
# Catacomb/Perl 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 Catacomb/Perl; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

#----- Abstract fields ------------------------------------------------------

package Catacomb::Field;
use Carp;
use Catacomb::Base;
use Catacomb::Cache;

$cache = Catacomb::Cache->new();

sub intern {
  croak("USage: Catacomb::Field::intern(f)") unless @_ == 1;
  my ($f) = @_;
  return $cache->intern($f);
}

sub elt {
  croak("Usage: Catacomb::Field::elt(f, x)") unless @_ == 2;
  my ($f, $x) = @_;
  return Catacomb::Field::Elt->new($f, $x);
}

sub zero {
  croak("Usage: Catacomb::Field::zero(f)") unless @_ == 1;
  my ($f) = @_;
  return Catacomb::Field::Elt->new($f, $f->_zero());
}
  
sub one {
  croak("Usage: Catacomb::Field::one(f)") unless @_ == 1;
  my ($f) = @_;
  return Catacomb::Field::Elt->new($f, $f->_one());
}

sub rand {
  croak("Usage: Catacomb::Field::rand(f, [rng])")
    unless @_ >= 1 && @_ <= 2;
  my ($f, $rng) = @_;
  $rng ||= $Catacomb::random;
  return Catacomb::Field::Elt->new($f, $f->_one($rng));
}

sub div {
  croak("Usage: Catacomb::Field::div(f, x, y)") unless @_ == 3;
  my ($f, $x, $y) = @_;
  return $f->mul($x, $f->inv($y));
}

sub primecurve {
  croak("Usage: Catacomb::Field::primecurve(f, a, b)") unless @_ == 3;
  my ($f, $a, $b) = @_;
  croak("not a prime field") unless $f->type == Catacomb::FTY_PRIME;
  return Catacomb::EC::Curve->prime($f, $a, $b);
}

sub primeprojcurve {
  croak("Usage: Catacomb::Field::primeprojcurve(f, a, b)") unless @_ == 3;
  my ($f, $a, $b) = @_;
  croak("not a prime field") unless $f->type == Catacomb::FTY_PRIME;
  return Catacomb::EC::Curve->primeproj($f, $a, $b);
}

sub bincurve {
  croak("Usage: Catacomb::Field::bincurve(f, a, b)") unless @_ == 3;
  my ($f, $a, $b) = @_;
  croak("not a prime field") unless $f->type == Catacomb::FTY_BINARY;
  return Catacomb::EC::Curve->bincurve($f, $a, $b);
}

sub binprojcurve {
  croak("Usage: Catacomb::Field::binprojcurve(f, a, b)") unless @_ == 3;
  my ($f, $a, $b) = @_;
  croak("not a prime field") unless $f->type == Catacomb::FTY_BINARY;
  return Catacomb::EC::Curve->binproj($f, $a, $b);
}

#----- Field elements -------------------------------------------------------

package Catacomb::Field::Elt;
use Carp;
use Catacomb::Base;

sub _elt { bless [@_], Catacomb::Field::Elt; }

sub new {
  croak("Usage: Catacomb::Field::Elt::new(me, f, x)"), unless @_ == 3;
  my ($me, $f, $x) = @_;
  my $r;
  ($f, $r) = $f->intern();
  return _elt($f->in($x), $f, $r);
}

sub field {
  croak("Usage: Catacomb::Field::Elt::field(e)") unless @_ == 1;
  return $_[0][1];
}

sub value {
  croak("Usage: Catacomb::Field::Elt::value(e)") unless @_ == 1;
  return $_[0][1]->out($_[0][0]);
}

sub elt {
  croak("Usage: Catacomb::Field::Elt::elt(e, x)") unless @_ == 2;
  my ($e, $x) = @_;
  my $f = $e->[1];
  return _elt($f->in($x), $f, $e->[2]);
}

sub tostring {
  croak("Usage: Catacomb::Field::Elt::tostring(e, [radix])")
    unless @_ >= 1 && @_ <= 2;
  my ($e, $radix) = @_;
  $radix = 16 unless defined($radix);
  return $e->value()->tostring($radix);
}

sub _convert {
  my ($x, $f, $r) = @_;
  if (UNIVERSAL::isa($x, Catacomb::Field::Elt)) {
    croak("field mismatch") unless $f == $x->[1];
    return $x;
  }
  if ($x == 0) {
    return _elt($f->_zero(), $f, $r);
  }
  if ($x == 1) {
    return _elt($f, $f->_one(), $f, $r);
  }
  croak("can't convert to field element");
}

sub _binop {
  my ($op, $x, $y, $swap) = @_;
  my $f = $x->[1];
  my $r = $x->[2];
  $y = _convert($y, $f, $r);
  my $z = $swap ?
    &$op($f, $x->[0], $y->[0]) :
    &$op($f, $y->[0], $x->[0]);
  return _elt($z, $f, $r);
}
  
sub _unop {
  my ($op, $x) = @_;
  my $f = $x->[1];
  my $r = $x->[2];
  my $z = &$op($f, $x->[0]);
  return _elt($z, $f, $r);
}

sub exp {
  croak("Usage: Catacomb::Field::Elt::exp(x, n)") unless @_ == 2;
  my ($x, $n) = @_;
  my ($xx, $f, $fr) = @$x;
  return _elt($f->exp($xx, $n), $f, $fr);
}

sub sqrt {
  croak("Usage: Catacomb::Field::Elt::sqrt(x)") unless @_ == 1;
  my ($x) = @_;
  my ($xx, $f, $fr) = @$x;
  return _elt($f->sqrt($xx), $f, $fr);
}

sub zerop {
  croak("Usage: Catacomb::Field::Elt::zerop(x)") unless @_ == 1;
  my ($x) = @_;
  my ($xx, $f, $fr) = @$x;
  return $f->zero($xx);
}

sub _eq {
  my ($x, $y) = @_;
  $y = _convert($y, $x->[1], $x->[2]);
  return Catacomb::MP::eq($x->[0], $y->[0]);
}

use overload
  '+' => sub { _binop(\&Catacomb::Field::add, @_); },
  '-' => sub { _binop(\&Catacomb::Field::sub, @_); },
  '*' => sub { _binop(\&Catacomb::Field::mul, @_); },
  '/' => sub { _binop(\&Catacomb::Field::div, @_); },
  '**' => sub { &exp($_[0], $_[1]); },
  '==' => sub { _eq(@_); },
  '!=' => sub { !_eq(@_); },
  'eq' => sub { _eq(@_); },
  'ne' => sub { !_eq(@_); },
  '""' => sub { "0x" . $_[0]->tostring(16); },
  '0+' => sub { $_[0][1]->toint(); },
  'sqrt' => sub { _unop(\&Catacomb::Field::sqrt, @_); },
  'neg' => sub { _unop(\&Catacomb::Field::neg, @_); };

sub inv { _unop(\&Catacomb::Field::inv, @_); }

#----- That's all, folks ----------------------------------------------------
