/*
 * This file is part of the Alliance CAD System
 * Copyright (C) Laboratoire LIP6 - Département ASIM
 * Universite Pierre et Marie Curie
 *
 * Home page          : http://www-asim.lip6.fr/alliance/
 * E-mail             : mailto:alliance-users@asim.lip6.fr
 *
 * This progam 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.
 *
 * Alliance VLSI  CAD System  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 the GNU C Library; see the  file COPYING. If not, write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/*
 * Tool    : BooG - negativ operands
 * Date    : 2000
 * Author  : Francois Donnet
 */


#include <stdlib.h>
#include <mut.h>
#include <abl.h>
#include <abe.h>
#include "bog_normalize_ARITY.h"
#include "bog_lib_negativ.h"



/****************************************************************************/
/* build a tree on negativ logic by propagation of NOT to the leaves        */
/* negativ is set to 1 to return the opposite abl (also in negativ logic)   */
/****************************************************************************/
extern chain_list* inv_oper(chain_list* abl, int negativ)
{
   chain_list* chain;
   
   if (ABL_ATOM(abl)) {
      if (ABL_ATOM_VALUE(abl)==getablatomzero()) {
         if (negativ) ABL_CAR_L(abl)=getablatomone();
      }
      else if (ABL_ATOM_VALUE(abl)==getablatomone()) {
         if (negativ) ABL_CAR_L(abl)=getablatomzero();
      }
      else {
         if (negativ) {
            abl=createablnotexpr(abl);
            ABL_ARITY_L(abl)=(chain_list*)1;
         }   
      }
      return abl;
   }

   switch (ABL_OPER(abl)) {
      case ABL_AND: 
         if (negativ) ABL_OPER_L(abl)=(chain_list*)ABL_NAND;
         else ABL_OPER_L(abl)=(chain_list*)ABL_NOR;
         negativ=!negativ;
         break;
      case ABL_NAND: 
         if (negativ) ABL_OPER_L(abl)=(chain_list*)ABL_NOR;
         else ABL_OPER_L(abl)=(chain_list*)ABL_NAND;
         break;
      case ABL_OR:
         if (negativ) ABL_OPER_L(abl)=(chain_list*)ABL_NOR;
         else ABL_OPER_L(abl)=(chain_list*)ABL_NAND;
         negativ=!negativ;
         break;
      case ABL_NOR:
         if (negativ) ABL_OPER_L(abl)=(chain_list*)ABL_NAND;
         else ABL_OPER_L(abl)=(chain_list*)ABL_NOR;
         break;
      case ABL_XOR:
         if (negativ) ABL_OPER_L(abl)=(chain_list*)ABL_NXOR;
         negativ=0;
         /*nothing to do:  same size XOR and NXOR*/
         break;
      case ABL_NXOR:
         if (negativ) ABL_OPER_L(abl)=(chain_list*)ABL_XOR;
         negativ=0;
         /*nothing to do*/
         break;
      case ABL_NOT:   /*erase NOT*/
         chain=abl;
         abl=inv_oper(ABL_CADR(abl),!negativ);
         freechain(chain);
         return abl;
      case ABL_STABLE:
         if (negativ) {
            abl=createablnotexpr(abl);
            ABL_ARITY_L(abl)=(chain_list*)1;
         }   
         return abl;
      default:
         fprintf(stderr,"inv_oper: unknown operator %ld\n",ABL_OPER(abl));
         exit(1);
   }

   for (chain=ABL_CDR(abl); chain; chain=ABL_CDR(chain)) {
      ABL_CAR_L(chain)=inv_oper(ABL_CAR(chain),negativ);
   }
   
   return abl;
}


/******************************************************************************/
/*return the abl in negativ logic                                             */
/*we need 2 levels minimum of operator to invert an abl                       */
/*if only 1 level or neither odd nor even level return an unchanged abl       */
/* minimize NOT operators number                                              */
/*parameter is deleted                                                        */
/******************************************************************************/
extern chain_list* build_negativ(chain_list* abl)
{
   int to_inv=0;  /*leaf to change*/
   int to_let=0;  /*leaf to let like this*/
   chain_list *leaf=NULL, *chain; 
     
   if (ABL_ATOM(abl)) return abl;
   /*NOT special case try to erase it*/
   if (ABL_OPER(abl)==ABL_NOT) {
      /*at the end, nothing to do*/
      if (ABL_ATOM(ABL_CADR(abl))) return abl;
      /*propagate inverters to the leaves*/
      abl=inv_oper(abl,0);
      /*abl has changed, try again*/
      return build_negativ(abl);
   }  
   
   if (ABL_OPER(abl)==ABL_STABLE) return abl;
   
   for (chain=ABL_CDR(abl); chain; chain=ABL_CDR(chain)) {
      ABL_CAR_L(chain)=build_negativ(ABL_CAR(chain));
   }

   for (chain=ABL_CDR(abl); chain; chain=ABL_CDR(chain)) {
      leaf=ABL_CAR(chain);
      if (ABL_ATOM(leaf)) {
         /*constant undifferent*/
         if (ABL_ATOM_VALUE(leaf)!=getablatomzero()
          && ABL_ATOM_VALUE(leaf)!=getablatomone()) to_let++;
         continue;
      }
      switch (ABL_OPER(leaf)) {
         case ABL_AND: case ABL_OR: case ABL_NOT: to_inv++; break;
         case ABL_NAND: case ABL_NOR: case ABL_STABLE: to_let++; break;
         case ABL_XOR: case ABL_NXOR: /*undifferent*/ break;
         default:
            fprintf(stderr,"build_negativ: oper %ld unknown\n",ABL_OPER(leaf));
            exit(1);
      }
   }
   
   /*no gain, do not change*/
   if (to_let>to_inv) return abl;
   else if (to_let==to_inv) {
         /*no advantage to invert*/
      switch (ABL_OPER(abl)) {
         case ABL_NAND: case ABL_NOR: return abl; 
      }
   }  
   
   /*forbidden to invert*/
   switch (ABL_OPER(abl)) {
         case ABL_STABLE: 
         case ABL_NXOR: case ABL_XOR: return abl; 
   }
   
   for (chain=ABL_CDR(abl); chain; chain=ABL_CDR(chain)) {
      leaf=ABL_CAR(chain);
      if (ABL_ATOM(leaf)) {
         /*constant undifferent*/
         if (ABL_ATOM_VALUE(leaf)==getablatomzero()) 
            ABL_CAR_L(leaf)=getablatomone();
         else if (ABL_ATOM_VALUE(leaf)==getablatomone()) 
            ABL_CAR_L(leaf)=getablatomzero();
         else {
            ABL_CAR_L(chain)=createablnotexpr(ABL_CAR(chain));
            ABL_ARITY_L(ABL_CAR(chain))=(chain_list*)1;
         }   
         continue;
      }
      else if (ABL_OPER(leaf)==ABL_STABLE) {
         ABL_CAR_L(chain)=createablnotexpr(ABL_CAR(chain));
         ABL_ARITY_L(ABL_CAR(chain))=(chain_list*)1;
         continue;
      }
      switch (ABL_OPER(leaf)) {
         case ABL_AND: ABL_OPER_L(leaf)=(chain_list*)ABL_NAND; break; 
         case ABL_OR: ABL_OPER_L(leaf)=(chain_list*)ABL_NOR; break; 
         case ABL_NAND: ABL_OPER_L(leaf)=(chain_list*)ABL_AND; break; 
         case ABL_NOR: ABL_OPER_L(leaf)=(chain_list*)ABL_OR; break; 
         case ABL_XOR: ABL_OPER_L(leaf)=(chain_list*)ABL_NXOR; break; 
         case ABL_NXOR: ABL_OPER_L(leaf)=(chain_list*)ABL_XOR; break; 
         case ABL_NOT: 
            ABL_CAR_L(chain)=ABL_CADR(leaf);
            freechain(leaf);
            break;
         default:
            fprintf(stderr,"build_negativ: oper %ld unknown\n",ABL_OPER(leaf));
            exit(1);
      }
   }
   
   switch (ABL_OPER(abl)) {
      case ABL_AND: ABL_OPER_L(abl)=(chain_list*)ABL_NOR; break; 
      case ABL_OR: ABL_OPER_L(abl)=(chain_list*)ABL_NAND; break; 
      case ABL_NAND: ABL_OPER_L(abl)=(chain_list*)ABL_OR; break; 
      case ABL_NOR: ABL_OPER_L(abl)=(chain_list*)ABL_AND; break; 
      case ABL_NOT: 
         chain=abl;
         abl=ABL_CADR(abl);
         freechain(chain);
         break;
      default:
         fprintf(stderr,"build_negativ: oper %ld unknown\n",ABL_OPER(leaf));
         exit(1);
   }
   
   return abl;
}


