/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/*
  Part of the Processing project - http://processing.org

  Copyright (c) 2005 Ben Fry and Casey Reas
  Copyright (c) 2003 Martin Gomez, Ateneo de Manila University

  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
*/

package processing.app.tools;

import processing.app.*;
//import processing.core.*;

import java.io.*;
import java.util.StringTokenizer;


/**
 * Alternate handler for dealing with auto format,
 * contributed by Martin Gomez.
 */
public class AutoFormat {
  Editor editor;


  public AutoFormat(Editor editor) {
    this.editor = editor;
  }


  /*
  public void show() {
    String prog = editor.textarea.getText();

    // TODO re-enable history
    //history.record(prog, SketchHistory.BEAUTIFY);

    //int tabSize = Preferences.getInteger("editor.tabs.size");

    char program[] = prog.toCharArray();
    StringBuffer buffer = new StringBuffer();
    boolean gotBlankLine = false;
    int index = 0;
    int level = 0;

    while (index != program.length) {
      int begin = index;
      while ((program[index] != '\n') &&
             (program[index] != '\r')) {
        index++;
        if (program.length == index)
          break;
      }
      int end = index;
      if (index != program.length) {
        if ((index+1 != program.length) &&
            // treat \r\n from windows as one line
            (program[index] == '\r') &&
            (program[index+1] == '\n')) {
          index += 2;
        } else {
          index++;
        }
      } // otherwise don't increment

      String line = new String(program, begin, end-begin);
      line = line.trim();

      if (line.length() == 0) {
        if (!gotBlankLine) {
          // let first blank line through
          buffer.append('\n');
          gotBlankLine = true;
        }
      } else {
        //System.out.println(level);
        int idx = -1;
        String myline = line.substring(0);
        while (myline.lastIndexOf('}') != idx) {
          idx = myline.indexOf('}');
          myline = myline.substring(idx+1);
          level--;
        }
        //for (int i = 0; i < level*2; i++) {
        // TODO i've since forgotten how i made this work (maybe it's even
        //      a bug) but for now, level is incrementing/decrementing in
        //      steps of two. in the interest of getting a release out,
        //      i'm just gonna roll with that since this function will prolly
        //      be replaced entirely and there are other things to worry about.
        for (int i = 0; i < tabSize * level / 2; i++) {
          buffer.append(' ');
        }
        buffer.append(line);
        buffer.append('\n');
        //if (line.charAt(0) == '{') {
        //level++;
        //}
        idx = -1;
        myline = line.substring(0);
        while (myline.lastIndexOf('{') != idx) {
          idx = myline.indexOf('{');
          myline = myline.substring(idx+1);
          level++;
        }
        gotBlankLine = false;
      }
    }

    // save current (rough) selection point
    int selectionEnd = editor.textarea.getSelectionEnd();

    // replace with new bootiful text
    editor.setText(buffer.toString(), false);

    // make sure the caret would be past the end of the text
    if (buffer.length() < selectionEnd - 1) {
      selectionEnd = buffer.length() - 1;
    }

    // at least in the neighborhood
    editor.textarea.select(selectionEnd, selectionEnd);

    editor.sketch.setModified();
    //buttons.clear();
  }
  */


  StringBuffer strOut;
  String formattedText;
  int indentValue;
  String indentChar;
  String uhOh = null;
  String theStuff;
  int EOF;
  BufferedInputStream bin = null;
  int nBytesRead, indexBlock, lineLength, lineNumber;
  byte bArray[];
  String strBlock;
  int s_level[];
  int c_level;
  int sp_flg[][];
  int s_ind[][];
  int s_if_lev[];
  int s_if_flg[];
  int if_lev, if_flg, level;
  int ind[];
  int e_flg, paren;
  static int p_flg[];
  char l_char, p_char;
  int a_flg, q_flg, ct;
  int s_tabs[][];
  String w_if_, w_else, w_for, w_ds, w_case, w_cpp_comment, w_jdoc;
  int jdoc, j;
  int BLOCK_MAXLEN;
  char string[];
  byte bstring[];
  byte bblank;
  char cc;
  int s_flg, b_flg;
  int peek;
  char peekc;
  int tabs;
  char next_char, last_char;
  char lastc0, lastc1;
  char c, c0;
  char w_kptr;

  String line_feed;

  static int outfil;  // temporary


  public void comment() {
    int save_s_flg;
    save_s_flg = s_flg;

    int done = 0;
    c = string[j++] = getchr(); // extra char
    while (done == 0) {
      c = string[j++] = getchr();
      while(c != '/') {
        if(c == '\n' || c == '\r') {
          lineNumber++;
          putcoms();
          s_flg = 1;
        }
        c = string[j++] = getchr();
      }
      String tmpstr = new String(string);
      if (j>1 && string[j-2] == '*') {
        done = 1;
        jdoc = 0;
      }
    }

    putcoms();
    s_flg = save_s_flg;
    jdoc = 0;
    return;
  }


  public char get_string() {
    char ch;
    ch = '*';
    while (true) {
      switch (ch) {
      default:
        ch = string[j++] = getchr();
        if (ch == '\\') {
          string[j++] = getchr();
          break;
        }
        if (ch == '\'' || ch == '"') {
          cc = string[j++] = getchr();
          while (cc != ch) {
            if (cc == '\\') string[j++] = getchr();
            cc = string[j++] = getchr();
          }
          break;
        }
        if (ch == '\n' || ch == '\r') {
          indent_puts();
          a_flg = 1;
          break;
        } else {
          return(ch);
        }
      }
    }
  }


  public void indent_puts() {
    string[j] = '\0';
    if (j > 0) {
      if (s_flg != 0) {
        if((tabs > 0) && (string[0] != '{') && (a_flg == 1)) {
          tabs++;
        }
        p_tabs();
        s_flg = 0;
        if ((tabs > 0) && (string[0] != '{') && (a_flg == 1)) {
          tabs--;
        }
        a_flg = 0;
      }
      String j_string = new String(string);
      strOut.append(j_string.substring(0,j));
      for (int i=0; i<j; i++) string[i] = '\0';
      j = 0;

    } else {
      if (s_flg != 0) {
        s_flg = 0;
        a_flg = 0;
      }
    }
  }


  public void fprintf(int outfil, String out_string) {
    int out_len = out_string.length();
    String j_string = new String(string);
    strOut.append(out_string);
  }


  public int grabLines() {
    return lineNumber;
  }


  public void setUhOh(String s) {
    uhOh = s;
  }


  public String grabUhOh() {
    return uhOh;
  }


  public void show() {
    StringBuffer onechar;

    theStuff = editor.textarea.getText();
    strOut = new StringBuffer();
    indentValue = Preferences.getInteger("editor.tabs.size");
    indentChar = new String(" ");

    lineNumber = 0;
    BLOCK_MAXLEN = 256;
    c_level = if_lev = level = e_flg = paren = 0;
    a_flg = q_flg = j = b_flg = tabs = 0;
    if_flg = peek = -1;
    peekc = '`';
    s_flg = 1;
    bblank = ' ';
    jdoc = 0;

    s_level  = new int[10];
    sp_flg   = new int[20][10];
    s_ind    = new int[20][10];
    s_if_lev = new int[10];
    s_if_flg = new int[10];
    ind      = new int[10];
    p_flg    = new int[10];
    s_tabs   = new int[20][10];

    w_else = new String ("else");
    w_if_ = new String ("if");
    w_for = new String ("for");
    w_ds  = new String ("default");
    w_case  = new String ("case");
    w_cpp_comment = new String ("//");
    w_jdoc = new String ("/**");
    line_feed = new String ("\n");

    try {   // opening input string
      // open for input
      ByteArrayInputStream in =
        new ByteArrayInputStream(theStuff.getBytes());

      // add buffering to that InputStream
      bin = new BufferedInputStream(in);

    } catch(Exception e) {
      System.out.println(e.toString());
    }

    // read as long as there is something to read
    EOF = 0;  // = 1 set in getchr when EOF

    bArray = new byte[BLOCK_MAXLEN];
    string = new char[BLOCK_MAXLEN];
    try {  // the whole process
      for (int ib = 0; ib < BLOCK_MAXLEN; ib++) bArray[ib] = '\0';

      lineLength = nBytesRead = 0;
      // read up a block - remember how many bytes read
      nBytesRead = bin.read(bArray);
      strBlock = new String(bArray);

      lineLength = nBytesRead;
      lineNumber  = 1;
      indexBlock = -1;
      j = 0;
      while(EOF == 0)
      {
        c = getchr();
        switch(c)
        {
        default:
          string[j++] = c;
          if(c != ',')
          {
            l_char = c;
          }
          break;
        case ' ':
        case '\t':
          if(lookup(w_else) == 1)
          {
            gotelse();
            if(s_flg == 0 || j > 0)string[j++] = c;
            indent_puts();
            s_flg = 0;
            break;
          }
          if(s_flg == 0 || j > 0)string[j++] = c;
          break;
        case '\r':                    /* <CR> for MS Windows 95 */
        case '\n':
          lineNumber++;
          if (EOF==1)
          {
            break;
          }
          String j_string = new String(string);

          e_flg = lookup(w_else);
          if(e_flg == 1) gotelse();
          if (lookup_com(w_cpp_comment) == 1)
          {
            if (string[j] == '\n')
            {
              string[j] = '\0';
              j--;
            }
          }

          indent_puts();
          fprintf(outfil, line_feed);
          s_flg = 1;
          if(e_flg == 1)
          {
            p_flg[level]++;
            tabs++;
          }
          else
            if(p_char == l_char)
            {
              a_flg = 1;
            }
          break;
        case '{':
          if(lookup(w_else) == 1)gotelse();
          s_if_lev[c_level] = if_lev;
          s_if_flg[c_level] = if_flg;
          if_lev = if_flg = 0;
          c_level++;
          if(s_flg == 1 && p_flg[level] != 0)
          {
            p_flg[level]--;
            tabs--;
          }
          string[j++] = c;
          indent_puts();
          getnl() ;
          indent_puts();
          fprintf(outfil,"\n");
          tabs++;
          s_flg = 1;
          if(p_flg[level] > 0)
          {
            ind[level] = 1;
            level++;
            s_level[level] = c_level;
          }
          break;
        case '}':
          c_level--;
          if (c_level < 0)
          {
            EOF = 1;
            string[j++] = c;
            indent_puts();
            break;
          }
          if((if_lev = s_if_lev[c_level]-1) < 0)if_lev = 0;
          if_flg = s_if_flg[c_level];
          indent_puts();
          tabs--;
          p_tabs();
          peekc = getchr();
          if( peekc == ';')
          {
            onechar = new StringBuffer();
            onechar.append(c);                        /* } */
            onechar.append(';');
            fprintf(outfil, onechar.toString());
            peek = -1;
            peekc = '`';
          }
          else
          {
            onechar = new StringBuffer();
            onechar.append(c);
            fprintf(outfil, onechar.toString());
            peek = 1;
          }
          getnl();
          indent_puts();
          fprintf(outfil,"\n");
          s_flg = 1;
          if(c_level < s_level[level])
            if(level > 0) level--;
          if(ind[level] != 0)
          {
            tabs -= p_flg[level];
            p_flg[level] = 0;
            ind[level] = 0;
          }
          break;
        case '"':
        case '\'':
          string[j++] = c;
          cc = getchr();
          while(cc != c)
          {
            // max. length of line should be 256
            string[j++] = cc;

            if(cc == '\\')
            {
              cc = string[j++] = getchr();
            }
            if(cc == '\n')
            {
              lineNumber++;
              indent_puts();
              s_flg = 1;
            }
            cc = getchr();

          }
          string[j++] = cc;
          if(getnl() == 1)
          {
            l_char = cc;
            peek = 1;
            peekc = '\n';
          }
          break;
        case ';':
          string[j++] = c;
          indent_puts();
          if(p_flg[level] > 0 && ind[level] == 0)
          {
            tabs -= p_flg[level];
            p_flg[level] = 0;
          }
          getnl();
          indent_puts();
          fprintf(outfil,"\n");
          s_flg = 1;
          if(if_lev > 0)
            if(if_flg == 1)
            {
              if_lev--;
              if_flg = 0;
            }
            else if_lev = 0;
          break;
        case '\\':
          string[j++] = c;
          string[j++] = getchr();
          break;
        case '?':
          q_flg = 1;
          string[j++] = c;
          break;
        case ':':
          string[j++] = c;
          peekc = getchr();
          if(peekc == ':')
          {
            indent_puts();
            fprintf (outfil,":");
            peek = -1;
            peekc = '`';
            break;
          }
          else
          {
            int double_colon = 0;
            peek = 1;
          }

          if(q_flg == 1)
          {
            q_flg = 0;
            break;
          }
          if(lookup(w_ds) == 0 && lookup(w_case) == 0)
          {
            s_flg = 0;
            indent_puts();
          }
          else
          {
            tabs--;
            indent_puts();
            tabs++;
          }
          peekc = getchr();
          if(peekc == ';')
          {
            fprintf(outfil,";");
            peek = -1;
            peekc = '`';
          }
          else
          {
            peek = 1;
          }
          getnl();
          indent_puts();
          fprintf(outfil,"\n");
          s_flg = 1;
          break;

        case '/':
          c0 = string[j];
          string[j++] = c;
          peekc = getchr();

          if(peekc == '/')
          {
            string[j++] = peekc;
            peekc = '`';
            peek = -1;
            cpp_comment();
            fprintf(outfil,"\n");
            break;
          }
          else
          {
            peek = 1;
          }

          if(peekc != '*') {
            break;
          }
          else
          {
            if (j > 0) string[j--] = '\0';
            if (j > 0) indent_puts();
            string[j++] = '/';
            string[j++] = '*';
            peek = -1;
            peekc = '`';
            comment();
            break;
          }
        case '#':
          string[j++] = c;
          cc = getchr();
          while(cc != '\n')
          {
            string[j++] = cc;
            cc = getchr();
          }
          string[j++] = cc;
          s_flg = 0;
          indent_puts();
          s_flg = 1;
          break;
        case ')':
          paren--;
          if (paren < 0)
          {
            EOF = 1;
          }
          string[j++] = c;
          indent_puts();
          if(getnl() == 1)
          {
            peekc = '\n';
            peek = 1;
            if(paren != 0)
            {
              a_flg = 1;
            }
            else if(tabs > 0)
            {
              p_flg[level]++;
              tabs++;
              ind[level] = 0;
            }
          }
          break;
        case '(':
          string[j++] = c;
          paren++;
          if ((lookup(w_for) == 1))
          {
            c = get_string();
            while(c != ';') c = get_string();
            ct=0;
            int for_done = 0;
            while (for_done==0)
            {
              c = get_string();
              while(c != ')')
              {
                if(c == '(') ct++;
                c = get_string();
              }
              if(ct != 0)
              {
                ct--;
              }
              else for_done = 1;
            }                        /* endwhile for_done */
            paren--;
            if (paren < 0)
            {
              EOF = 1;
            }
            indent_puts();
            if(getnl() == 1)
            {
              peekc = '\n';
              peek = 1;
              p_flg[level]++;
              tabs++;
              ind[level] = 0;
            }
            break;
          }

          if(lookup(w_if_) == 1)
          {
            indent_puts();
            s_tabs[c_level][if_lev] = tabs;
            sp_flg[c_level][if_lev] = p_flg[level];
            s_ind[c_level][if_lev] = ind[level];
            if_lev++;
            if_flg = 1;
          }
        }                /* end switch */

        String j_string = new String(string);

      } // end while not EOF

      //formattedText = strOut.toString();

      // save current (rough) selection point
      int selectionEnd = editor.textarea.getSelectionEnd();

      // make sure the caret would be past the end of the text
      if (strOut.length() < selectionEnd - 1) {
        selectionEnd = strOut.length() - 1;
      }

      // replace with new bootiful text
      // selectionEnd hopefully at least in the neighborhood
      editor.setText(strOut.toString(), selectionEnd, selectionEnd);

      editor.sketch.setModified();

      bin.close(); // close buff

    } catch (IOException ioe) {
      editor.error(ioe);
      //ioe.printStackTrace();
    }


    // () {} check

    String ck_paren = new String("left");
    if (paren < 0) ck_paren = "right";

    if (paren != 0) {
      setUhOh("Uh oh... too many " + ck_paren + " parentheses.");

    } else {  // check braces only if parens are ok
      ck_paren = "left";
      if (c_level < 0) {
        ck_paren = "right";
      } else if (c_level != 0) {
        setUhOh("Uh oh... too many " + ck_paren + " curled braces.");
      }
    }
  }


  /* throw back the stuff to the editor */
  public String getFormattedText()
  {
    return formattedText;
  }


  /* special edition of put string for comment processing */
  public void putcoms()
  {
    int i = 0;
    int sav_s_flg = s_flg;
    if(j > 0)
    {
      if(s_flg != 0)
      {
        p_tabs();
        s_flg = 0;
      }
      string[j] = '\0';
      i = 0;
      while (string[i] == ' ') i++;
      if (lookup_com(w_jdoc) == 1) jdoc = 1;
      String strBuffer = new String(string,0,j);
      if (string[i] == '/' && string[i+1]=='*')
      {
        if ((last_char != ';') && (sav_s_flg==1) )
        {
          fprintf(outfil, strBuffer.substring(i,j));
        }
        else
        {
          fprintf(outfil, strBuffer);
        }
      }
      else
      {
        if (string[i]=='*' || jdoc == 0)
          fprintf (outfil, " "+strBuffer.substring(i,j));
        else
          fprintf (outfil, " * "+strBuffer.substring(i,j));
      }
      j = 0;
      string[0] = '\0';
    }
  }

  public void cpp_comment()
  {
    c = getchr();
    while(c != '\n' && c != '\r' && j<133)
    {
      string[j++] = c;
      c = getchr();
    }
    lineNumber++;
    indent_puts();
    s_flg = 1;
  }


  /* expand indentValue into tabs and spaces */
  public void p_tabs()
  {
    int i,k;

    if (tabs<0) tabs = 0;
    if (tabs==0) return;
    i = tabs * indentValue;  // calc number of spaces
    //j = i/8;        /* calc number of tab chars */

    for (k=0; k < i; k++) {
      strOut.append(indentChar);
    }
  }


  public char getchr()
  {
    if((peek < 0) && (last_char != ' ') && (last_char != '\t'))
    {
      if((last_char != '\n') && (last_char != '\r'))
        p_char = last_char;
    }
    if(peek > 0)        /* char was read previously */
    {
      last_char = peekc;
      peek = -1;
    }
    else                    /* read next char in string */
    {
      indexBlock++;
      if (indexBlock >= lineLength)
      {
        for (int ib=0; ib<nBytesRead; ib++) bArray[ib] = '\0';

        lineLength = nBytesRead = 0;
        try /* to get the next block */
        {
          if (bin.available() > 0)
          {
            nBytesRead = bin.read(bArray);
            lineLength = nBytesRead;
            strBlock = new String(bArray);
            indexBlock = 0;
            last_char = strBlock.charAt(indexBlock);
            peek = -1;
            peekc = '`';
          }
          else
          {
            EOF = 1;
            peekc  = '\0';
          }
        }
        catch(IOException ioe)
        {
          System.out.println(ioe.toString());
        }
      }
      else
      {
        last_char = strBlock.charAt(indexBlock);
      }
    }
    peek = -1;
    if (last_char == '\r')
    {
      last_char = getchr();
    }

    return last_char;
  }

  /* else processing */
  public void gotelse()
  {
    tabs = s_tabs[c_level][if_lev];
    p_flg[level] = sp_flg[c_level][if_lev];
    ind[level] = s_ind[c_level][if_lev];
    if_flg = 1;
  }

  /* read to new_line */
  public int getnl()
  {
    int save_s_flg;
    save_s_flg = tabs;
    peekc = getchr();
    while(peekc == '\t' || peekc == ' ')
    {
      string[j++] = peekc;
      peek = -1;
      peekc = '`';
      peekc = getchr();
      peek = 1;
    }
    peek = 1;

    if (peekc == '/')
    {
      peek = -1;
      peekc = '`';
      peekc = getchr();
      if (peekc == '*')
      {
        string[j++] = '/';
        string[j++] = '*';
        peek = -1;
        peekc = '`';
        comment();
      }
      else if (peekc == '/')
      {
        string[j++] = '/';
        string[j++] = '/';
        peek = -1;
        peekc = '`';
        cpp_comment();
        return (1);
      }
      else
      {
        string[j++] = '/';
        peek = 1;
      }
    }
    peekc = getchr();
    if(peekc == '\n')
    {
      lineNumber++;
      peek = -1;
      peekc = '`';
      tabs = save_s_flg;
      return(1);
    }
    else
    {
      peek = 1;
    }
    return 0;
  }

  public int lookup (String keyword)
  {
    char r;
    int  l,kk,k,i;
    String j_string = new String(string);

    if (j<1) return (0);
    kk=0;
    while(string[kk] == ' ')kk++;
    l=0;
    l = j_string.indexOf(keyword);
    if (l<0 || l!=kk)
    {
      return 0;
    }
    r = string[kk+keyword.length()];
    if(r >= 'a' && r <= 'z') return(0);
    if(r >= 'A' && r <= 'Z') return(0);
    if(r >= '0' && r <= '9') return(0);
    if(r == '_' || r == '&') return(0);
    return (1);
  }

  public int lookup_com (String keyword)
  {
    char r;
    int  l,kk,k,i;
    String j_string = new String(string);

    if (j<1) return (0);
    kk=0;
    while(string[kk] == ' ')kk++;
    l=0;
    l = j_string.indexOf(keyword);
    if (l<0 || l!=kk)
    {
      return 0;
    }
    return (1);
  }
}