import java.io.IOException;

import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyClass;
import org.jruby.RubyHash;
import org.jruby.RubyModule;
import org.jruby.RubyNumeric;
import org.jruby.RubyObject;
import org.jruby.RubyObjectAdapter;
import org.jruby.RubyRegexp;
import org.jruby.RubyString;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.javasupport.JavaEmbedUtils;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.callback.Callback;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.load.BasicLibraryService;
import org.jruby.util.ByteList;

public class HpricotCss {
    public void FILTER(String id) {
        IRubyObject[] args = new IRubyObject[fargs];
        System.arraycopy(fvals, 0, args, 0, fargs);
        mod.callMethod(ctx, id, args);
        tmpt.rb_clear();
        fargs = 1;
    }

    public void FILTERAUTO() {
        try {
            FILTER(new String(data, ts, te - ts, "ISO-8859-1"));
        } catch(java.io.UnsupportedEncodingException e) {}
    }

    public void PUSH(int aps, int ape) {
        RubyString str = RubyString.newString(runtime, data, aps, ape-aps);
        fvals[fargs++] = str;
        tmpt.append(str);
    }

    private IRubyObject self, mod, str, node;
    private int cs, act, eof, p, pe, ts, te, aps, ape, aps2, ape2;
    private byte[] data;

    private int fargs = 1;
    private IRubyObject[] fvals = new IRubyObject[6];
    private RubyArray focus;
    private RubyArray tmpt;
    private Ruby runtime;
    private ThreadContext ctx;

    public HpricotCss(IRubyObject self, IRubyObject mod, IRubyObject str, IRubyObject node) {
        this.self = self;
        this.mod = mod;
        this.str = str;
        this.node = node;
        this.runtime = self.getRuntime();
        this.ctx = runtime.getCurrentContext();
        this.focus = RubyArray.newArray(runtime, node);
        this.tmpt = runtime.newArray();

        fvals[0] = focus;

        if(!(str instanceof RubyString)) {
            throw runtime.newArgumentError("bad CSS selector, String only please.");
        }

        ByteList bl = ((RubyString)str).getByteList();

        data = bl.bytes;
        p = bl.begin;
        pe = p + bl.realSize;
        eof = pe;
    }

%%{
  machine hpricot_css;

  action a {
    aps = p;
  }

  action b {
    ape = p;
    PUSH(aps, ape);
  }

  action c {
    ape = p;
    aps2 = p;
  }

  action d {
    ape2 = p;
    PUSH(aps, ape);
    PUSH(aps2, ape2);
  }

  commas    = space* "," space*;
  traverse  = [>+~];
  sdot      = "\\.";
  utfw      = alnum | "_" | "-" |
              (0xc4 0xa8..0xbf) | (0xc5..0xdf 0x80..0xbf) |
              (0xe0..0xef 0x80..0xbf 0x80..0xbf) |
              (0xf0..0xf4 0x80..0xbf 0x80..0xbf 0x80..0xbf);
  utfword   = utfw+;
  utfname   = (utfw | sdot)+;
  quote1    = "'" [^']* "'";
  quote2    = '"' [^"]* '"';

  cssid     = "#" %a utfname %b;
  cssclass  = "." %a utfname %b;
  cssname   = "[name=" %a utfname %b "]";
  cssattr   = "[" %a utfname %c space* [^ \n\t]? "=" %d space* (quote1 | quote2 | [^\]]+) "]";
  csstag    = utfname >a %b;
  cssmod    = ("even" | "odd" | (digit | "n" | "+" | "-")* );
  csschild  = ":" %a ("only" | "nth" | "last" | "first") "-child" %b ("(" %a cssmod %b ")")?;
  csspos    = ":" %a ("nth" | "eq" | "gt" | "lt" | "first" | "last" | "even" | "odd") %b ("(" %a digit+ %b ")")?;
  pseudop   = "(" [^)]+ ")";
  pseudoq   = "'" (pseudop+ | [^'()]*) "'" |
              '"' (pseudop+ | [^"()]*) '"' |
                  (pseudop+ | [^"()]*);
  pseudo    = ":" %a utfname %b ("(" %a pseudoq %b ")")?;

  main     := |*
    cssid      => { FILTER("ID"); };
    cssclass   => { FILTER("CLASS"); };
    cssname    => { FILTER("NAME"); };
    cssattr    => { FILTER("ATTR"); };
    csstag     => { FILTER("TAG"); };
    cssmod     => { FILTER("MOD"); };
    csschild   => { FILTER("CHILD"); };
    csspos     => { FILTER("POS"); };
    pseudo     => { FILTER("PSUEDO"); };
    commas     => { focus = RubyArray.newArray(runtime, node); };
    traverse   => { FILTERAUTO(); };
    space;
  *|;

  write data nofinal;
}%%

    public IRubyObject scan() {
%% write init;
%% write exec;

        return focus;
    }
}
