(*
 * Copyright (c) 2001 Stefan Kral
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *)

open List
open Util
open Expr
open Number
open Variable
open VFpBasics
open VSimdBasics
open StateMonad
open BalanceVfpinstrs

(* monadic access functions *)
let fetchArrayInfoM = fetchStateM >>= fun s -> unitM (fst s)
let fetchVfpinstrsM = fetchStateM >>= fun s -> unitM (snd s)

let storeVfpinstrsM x = fetchStateM >>= fun s -> storeStateM (repl2of2 x s)

(****************************************************************************)

let addVfpinstrM i = fetchVfpinstrsM >>= consM i >>= storeVfpinstrsM

let arrayToTypeM key = fetchArrayInfoM >>= fun map -> unitM (assoc key map)

(****************************************************************************)

let arrayinfoToAccess = function
  | (ComplexArray _, RealPart) -> V_FPRealOfComplex
  | (ComplexArray _, ImagPart) -> V_FPImagOfComplex
  | (RealArray, RealPart)      -> V_FPReal
  | (RealArray, ImagPart)      -> failwith "arrayinfoToAccess"

let variableToTupledForm = function
  | RealArrayElem(arr,idx) -> (RealPart,arr,idx)
  | ImagArrayElem(arr,idx) -> (ImagPart,arr,idx)
  | _ 			   -> failwith "variableToTupledForm"

(****************************************************************************)

let conditionallyCopyVarM s = function
  | Some d when s <> d -> failwith "conditional_copy_var: unsupported!"
  | _ -> unitM s

let extractUseReturnVfpregM fM = function
  | Some x -> fM x >> unitM x
  | None   -> let x = makeNewVfpreg () in fM x >> unitM x

let extractUseVfpregM fM x = extractUseReturnVfpregM fM x >> unitM ()

let varexprToVfpreg = function
  | Temporary s -> V_FPReg s
  | _	 	-> failwith "varexprToVfpreg"

let timesexprToNormalized = function
  | (Num _, Num _)	-> failwith "timesexprToNormalized"
  | (Num _, _) as pair	-> pair
  | (x, (Num n as num))	-> (num,x)
  | pair		-> pair

let plusexprToChecked = function
  | []  -> failwith "plusexprToVfpinstrsM []"
  | [_] -> failwith "plusexprToVfpinstrsM [_]"
  | xs  -> xs 

let rec exprToNegated = function
  | Uminus x 	   -> x
  | Var x 	   -> Uminus (Var x)
  | Plus xs 	   -> Plus (map exprToNegated (plusexprToChecked xs))
  | Times(Num n,x) -> Times(Num (Number.negate n), x)
  | Times(x,Num n) -> Times(x, Num (Number.negate n))
  | Times(x,y) 	   -> Times(exprToNegated x, y) 
  | Integer i 	   -> Integer (-i)
  | Num n 	   -> Num (Number.negate n)

(****************************************************************************)

let rec summandCmp a b = match (a,b) with
  | (Uminus x, y) -> summandCmp x y
  | (x, Uminus y) -> summandCmp x y
  | (Var x, Var y) -> compare x y
  | (Var _, Times _) -> -1
  | (Times _, Var _) -> 1
  | (Times(Num _, Num _), _) -> failwith "summandCmp: not supported (1)!"
  | (_, Times(Num _, Num _)) -> failwith "summandCmp: not supported (2)!"
  | (Times(x, Num n), y) -> summandCmp (Times(Num n, x)) y
  | (x, Times(y, Num n)) -> summandCmp x (Times(Num n, y))
  | (Times(Num _, x), Times(Num _, y)) -> compare x y
  | (t1,t2) -> compare t1 t2

(****************************************************************************)

let rec exprToVfpinstrsM expr = 
  exprToVfpinstrsM' expr None

and exprToVfpinstrsM' = function
  | Var (Temporary _ as x) -> conditionallyCopyVarM (varexprToVfpreg x)
  | Var x      -> extractUseReturnVfpregM (loadToVfpinstrsM x)
  | Times(x,y) -> timesexprToVfpinstrsM (timesexprToNormalized (x,y))
  | Plus xs    -> plusexprToVfpinstrsM (plusexprToChecked xs)
  | Uminus (Var v) -> extractUseReturnVfpregM (uminusvarexprToVfpinstrM v)
  | Uminus x   -> exprToVfpinstrsM' (exprToNegated x)
  | Num _      -> failwith "exprToVfpinstrsM: Num _ not supported!"
  | Integer _  -> failwith "exprToVfpinstrsM: Integer _ not supported!"

and uminusvarexprToVfpinstrM v dst' =
  addVfpinstrM (V_FPUnaryOp(V_FPNegate,varexprToVfpreg v,dst'))

and timesexprToVfpinstrsM = function
  | (Num n, x) -> extractUseReturnVfpregM (timesconstexprToVfpinstrsM' x n)
  | (x1,x2)    -> extractUseReturnVfpregM (timesexprToVfpinstrsM' x1 x2)
and timesconstexprToVfpinstrsM' x n dst' =
  exprToVfpinstrsM x >>= fun x' ->
    addVfpinstrM (V_FPUnaryOp(V_FPMulConst(n),x',dst'))
and timesexprToVfpinstrsM' x1 x2 dst' =
  mapPairM exprToVfpinstrsM (x1,x2) >>= fun (x1',x2') ->
    addVfpinstrM (V_FPBinaryOp(V_FPMul,x1',x2',dst'))

and plusexprToVfpinstrsM xs dst =
  mapM summandToVfpsummandM (sort summandCmp xs) >>= fun xs' ->
    extractUseReturnVfpregM (fun dst' -> addVfpinstrM (V_FPAddL(xs',dst'))) dst
and summandToVfpsummandM = function
  | Uminus x -> exprToVfpinstrsM x >>= fun dst' -> unitM (V_FPMinus dst')
  | x        -> exprToVfpinstrsM x >>= fun dst' -> unitM (V_FPPlus dst')

(****************************************************************************)

and loadToVfpinstrsM src dst' =
  let (place,arr,idx) = variableToTupledForm src in
    arrayToTypeM arr >>= fun arrtype ->
      addVfpinstrM (V_FPLoad(arrayinfoToAccess (arrtype,place),arr,idx,dst'))

and storeToVfpinstrsM dst src' =
  let (place,arr,idx) = variableToTupledForm dst in
    arrayToTypeM arr >>= fun arrtype ->
      addVfpinstrM (V_FPStore(src',arrayinfoToAccess (arrtype,place),arr,idx))

(****************************************************************************)

let assignmentToVfpinstrsM = function
  | Assign(v,Var e) when is_input e || is_twiddle e ->
      if is_output v then
	extractUseReturnVfpregM (loadToVfpinstrsM e) None >>= 
	  storeToVfpinstrsM v
      else
        extractUseVfpregM (loadToVfpinstrsM e) (Some (varexprToVfpreg v))
  | Assign(v,e) when is_output v -> 
      exprToVfpinstrsM e >>=
        storeToVfpinstrsM v 
  | Assign(Temporary d,e) -> 
      ignoreM (exprToVfpinstrsM' e (Some (V_FPReg d)))
  | _ -> 
      failwith "assignmentToVfpinstrsM"

let assignmentsToVfpinstrsM xs =
  iterM assignmentToVfpinstrsM xs >>
    fetchVfpinstrsM >>= fun vfpinstrs_rev ->
      unitM (rev vfpinstrs_rev)

let assignmentsToVfpinstrs arraytypeinfo xs = 
  let _ = Util.info "assignmentsToVfpinstrs (1)" in
  let i1 = StateMonad.runM assignmentsToVfpinstrsM xs (arraytypeinfo,[]) in
  let _ = Util.info "assignmentsToVfpinstrs (2)" in
  let i2 = balance [] i1 in
  let _ = Util.info "assignmentsToVfpinstrs (3)" in
    map expandaddlistVfpinstr i2

