module ParserInFSharp 
open OpTyp
open System

// ermittelt den ersten Index des Zeichens 'c' in der Zeichenkette 'term'
let private getIndex(term:string, c:char) =
    let mutable index = -1
    let mutable niveauOfBrackets = 0
    for i=0 to term.Length-1 do 
         if term.[i]='(' then niveauOfBrackets <- niveauOfBrackets + 1
             else if term.[i]=')' then niveauOfBrackets <- niveauOfBrackets - 1
         if (term.[i] = c && niveauOfBrackets = 0) then index <- i
    index

// löscht umschließende Klammernm, sofern für den Teilterm nicht notwendig
let rec private deleteBrackets(term:string) =
    if term.StartsWith("(") && 
       term.EndsWith(")")   &&
       getIndex(term,'+') = -1  &&
       getIndex(term,'-') = -1  &&
       getIndex(term,'*') = -1  &&
       getIndex(term,'/') = -1  &&
       getIndex(term,'^') = -1 
       then term.Substring(1, term.Length-2)  |>  deleteBrackets
         else term

// fügt umschließende Klammern hinzu
let private setBrackets(term:string) =
            if term.Length > 1 then "(" + term +  ")"
               else term

// liefet das Argument einer (unären) Funktion, z.B. 'x+2' aus aus 'sin(x+2)'      
let private getArgument(term:string) = 
      let startPosition = term.IndexOf("(")
      term.Substring(startPosition+1,term.Length - (startPosition + 2))

// Termkorrekur aus '-sin("' wird zu '-1*sin("' 
let private termCorrection(term:string) =
    let list = ["-sin("; "-cos("; "-tan("; "-sqrt("; "-lg("; "-x("; "-("]
    let mutable result = term
    for partTerm in list do 
                 if term.StartsWith(partTerm) then result <- "-1*" + term.Substring(1) 
    result

// generiert aus der Angabe der Operation (OP_Sin, OP_Cos) die Zeichenkette für den Term, z.B. aus 'OP_Sin' -> 'sin(' 
let private getOpChar(op) = 
             match op with
               | Sin -> "sin("
               | Cos -> "cos("
               | Tan -> "tan("
               | Lg  -> "lg("
               | Sqr -> "sqrt("

// ergänzt fehlende '*'-Zeichen, z.B. aus '3x' wird '3*x'
let private CreateFullTerm(term:string) =       // Ergänzung fehlender '*'-Zeichen
    let mutable termNew = term
    let mutable pos = 1
    // Prüfung, ob vor diesen Zeichen  ein '*' eingefügt wird, wenn davor eine Zahl oder eine Variable steht
    let partTerms =["x"; "("; "sin"; "cos"; "tan"; "sqrt"; "lg"] 
    for c in partTerms do
       pos <- 1
       while pos < termNew.Length && pos > 0 do
          pos <- termNew.IndexOf(c, pos)
          if pos > 0 then 
             let subTerm = termNew.Substring(pos - 1, 1)
             if subTerm = "x" || fst(Double.TryParse(subTerm)) || subTerm=")" then 
<                    termNew <- termNew.Insert(pos, "*")
                    pos <- pos + 2
             else
                    pos <- pos + 1
    //  Prüfung, ob nach diesen Zeichen  ein '*' eingefügt wird, wenn davor eine Zahl oder eine Variable steht
    let partTerms =[")"] 
    for c in partTerms do
       pos <- 0
       while pos < (termNew.Length - 1) && pos > -1 do
          pos <- termNew.IndexOf(c, pos)
          if pos > -1 && pos < (termNew.Length - 1) then 
             let subTerm = termNew.Substring(pos + 1, 1)
             if subTerm = "x" || fst(Double.TryParse(subTerm))  then 
                    termNew <- termNew.Insert(pos + 1, "*")
                    pos <- pos + 2
             else
                    pos <- pos + 1
    termNew     

// aus dem Term (Zeichenkette) wird der Syntaxbaum generiert 
let rec MathFunction term =
    let term = CreateFullTerm(term) |> deleteBrackets |> termCorrection 
    if  getIndex(term,'+') > -1 then 
                                           let nodes = splitTermToNodes(term, getIndex(term,'+'))
                                           NodeOfBinaryOp(Add, fst(nodes), snd(nodes))
    else if    getIndex(term,'-') > 0 then 
                                           let nodes = splitTermToNodes(term, getIndex(term,'-'))
                                           NodeOfBinaryOp(Sub, fst(nodes), snd(nodes))
    else if    getIndex(term,'*') > -1 then 
                                           let nodes = splitTermToNodes(term, getIndex(term,'*'))
                                           NodeOfBinaryOp(Mul, fst(nodes), snd(nodes))
    else if    getIndex(term,'/') > -1 then 
                                           let nodes = splitTermToNodes(term, getIndex(term,'/'))
                                           NodeOfBinaryOp(Div, fst(nodes), snd(nodes))
    else if    getIndex(term,'^') > -1 then 
                                           let nodes = splitTermToNodes(term, getIndex(term,'^'))
                                           NodeOfBinaryOp(Pow, fst(nodes), snd(nodes))
    else if    term.StartsWith("sin(")  then
                                           let node = getArgument(term) |> MathFunction
                                           NodeOfUnaryOp(Sin, node)
    else if    term.StartsWith("cos(")  then
                                           let node = getArgument(term) |> MathFunction
                                           NodeOfUnaryOp(Cos, node)
    else if    term.StartsWith("tan(")  then
                                           let node = getArgument(term) |> MathFunction
                                           NodeOfUnaryOp(Tan, node)
    else if    term.StartsWith("sqrt(") then
                                           let node = getArgument(term) |> MathFunction
                                           NodeOfUnaryOp(Sqr, node)
    else if    term.StartsWith("lg(")  then
                                           let node = getArgument(term) |> MathFunction
                                           NodeOfUnaryOp(Lg, node)
    else if    term="x"                then
                                           VariableX   
    else if    term="pi"               then
                                           Pi              
    else 
               match Double.TryParse term with
                                          | true, res -> Value(Double.Parse(term))
                                          | _         -> OtherTerm(term)
     
// splittet einen Term (string) in Elemente des Syntaxbaums
and  splitTermToNodes (term:string, index:int) =
    let partialTerms = term.Substring(0,index), term.Substring(index+1, (term.Length-index-1))
    MathFunction(fst(partialTerms)), MathFunction(snd(partialTerms))
                                          
// setzt Klammern um eine Zeichenkette, sofern diese eine Addition oder Subtraktion darstellt
let  private setBrackets2(term:string) = 
    match MathFunction(term) with
    | NodeOfBinaryOp(op, left, right) -> match op with
                                                | Add   
                                                | Sub   -> "("+ term+")"
                                                | _        -> term
    | _                                      -> term