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

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

  Copyright (c) 2005-06 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, additional bug fixes by Ben Fry.
 */
public class AutoFormat {
  Editor editor;

  static final int BLOCK_MAXLEN = 1024;

  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;
  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 AutoFormat(Editor editor) {
    this.editor = editor;
  }


  public void comment() throws IOException {
    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 != '/') && (j < string.length)) {
        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() throws IOException {
    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) {
  public void fprintf(String out_string) {
    //int out_len = out_string.length();
    String j_string = new String(string);
    strOut.append(out_string);
  }


  public int grabLines() {
    return lineNumber;
  }


  /* 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));
          fprintf(strBuffer.substring(i,j));
        }
        else
        {
          //fprintf(outfil, strBuffer);
          fprintf(strBuffer);
        }
      }
      else
      {
        if (string[i]=='*' || jdoc == 0)
          //fprintf (outfil, " "+strBuffer.substring(i,j));
          fprintf (" "+strBuffer.substring(i,j));
        else
          //fprintf (outfil, " * "+strBuffer.substring(i,j));
          fprintf (" * "+strBuffer.substring(i,j));
      }
      j = 0;
      string[0] = '\0';
    }
  }

  public void cpp_comment() throws IOException
  {
    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() throws IOException
  {
    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
          {
            //System.out.println("eof a");
            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() throws IOException
  {
    int save_s_flg;
    save_s_flg = tabs;
    peekc = getchr();
    //while ((peekc == '\t' || peekc == ' ') &&
    //     (j < string.length)) {
    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);
  }


  public void show() {
    StringBuffer onechar;

    String originalText = 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");

    // 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
      // open for input
      ByteArrayInputStream in =
        new ByteArrayInputStream(originalText.getBytes());

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

      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);
          fprintf(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");
          fprintf("\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;
            //System.out.println("eof b");
            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);   // the }
            onechar.append(';');
            //fprintf(outfil, onechar.toString());
            fprintf(onechar.toString());
            peek = -1;
            peekc = '`';
          }
          else
          {
            onechar = new StringBuffer();
            onechar.append(c);
            //fprintf(outfil, onechar.toString());
            fprintf(onechar.toString());
            peek = 1;
          }
          getnl();
          indent_puts();
          //fprintf(outfil,"\n");
          fprintf("\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");
          fprintf("\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,":");
            fprintf(":");
            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,";");
            fprintf(";");
            peek = -1;
            peekc = '`';
          }
          else
          {
            peek = 1;
          }
          getnl();
          indent_puts();
          //fprintf(outfil,"\n");
          fprintf("\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");
            fprintf("\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;
            //System.out.println("eof c");
          }
          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;
              //System.out.println("eof d");
            }
            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

        //System.out.println("string len is " + string.length);
        //if (EOF == 1) System.out.println(string);
        String j_string = new String(string);

      } // end while not EOF

      /*
      int bad;
      while ((bad = bin.read()) != -1) {
        System.out.print((char) bad);
      }
      */
      /*
      char bad;
      //while ((bad = getchr()) != 0) {
      while (true) {
        getchr();
        if (peek != -1) {
          System.out.print(last_char);
        } else {
          break;
        }
      }
      */

      // 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;
      }

      bin.close(); // close buff

      String formattedText = strOut.toString();
      if (formattedText.equals(originalText)) {
        editor.message("No changes necessary for Auto Format.");

      } else if (paren != 0) {
        // warn user if there are too many parens in either direction
        editor.error("Auto Format Canceled: Too many " +
                       ((paren < 0) ? "right" : "left") +
                       " parentheses.");

        } else if (c_level != 0) {  // check braces only if parens are ok
        editor.error("Auto Format Canceled: Too many " +
                       ((c_level < 0) ? "right" : "left") +
                       " curly braces.");

        } else {
        // replace with new bootiful text
        // selectionEnd hopefully at least in the neighborhood
        editor.setText(formattedText, selectionEnd, selectionEnd);
        editor.sketch.setModified(true);
        // mark as finished
          editor.message("Auto Format finished.");
        }

    } catch (Exception e) {
      editor.error(e);
    }
  }
}