/*## (c) SECURITY EXPLORATIONS    2018 poland                                #*/
/*##     http://www.security-explorations.com                                #*/

/* THIS SOFTWARE IS PROTECTED BY DOMESTIC AND INTERNATIONAL COPYRIGHT LAWS    */
/* UNAUTHORISED COPYING OF THIS SOFTWARE IN EITHER SOURCE OR BINARY FORM IS   */
/* EXPRESSLY FORBIDDEN. ANY USE, INCLUDING THE REPRODUCTION, MODIFICATION,    */
/* DISTRIBUTION, TRANSMISSION, RE-PUBLICATION, STORAGE OR DISPLAY OF ANY      */
/* PART OF THE SOFTWARE, FOR COMMERCIAL OR ANY OTHER PURPOSES REQUIRES A      */
/* VALID LICENSE FROM THE COPYRIGHT HOLDER.                                   */

/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS    */
/* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,*/
/* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    */
/* SECURITY EXPLORATIONS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, */
/* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF  */
/* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE     */
/* SOFTWARE.                                                                  */

import java.lang.*;
import java.util.*;
import java.io.*;

public class Shell {
 private static ConsoleIf cl;

 public static class Option {
  public static final int OPT_PURE_ARG = 0x00;
  public static final int OPT_NO_ARG   = 0x01;
  public static final int OPT_INT_ARG  = 0x02;
  public static final int OPT_STR_ARG  = 0x03;

  int type;
  char option;
  Object arg;

  public Option(int type,Object arg) {
   this(type,(char)0,arg);
  }

  public Option(int type,char option,Object arg) {
   this.type=type;
   this.option=option;
   this.arg=arg;
  }

  public Option(int type,char option) {
   this.type=type;
   this.option=option;
   this.arg=null;
  }

  public int int_arg() {
   return ((Integer)arg).intValue();
  }

  public String str_arg() {
   return (String)arg;
  }

  public boolean pure_arg() {
   if (type==OPT_PURE_ARG) return true;
    else return false;
  }

  public boolean no_arg() {
   if (type==OPT_NO_ARG) return true;
    else return false;
  }

 }

 public static class Cmd {
  String name;
  Option options[];
  Interpreter.CmdDesc desc;

  public Cmd(String name,Option options[],Interpreter.CmdDesc desc) {
   this.name=name;
   this.options=options;
   this.desc=desc;
  }

 }

 public static final String BANNER = "# SRP-2018-02 client for ITI2850ST, ITI2849ST and ITI2851S\n# (c) SECURITY EXPLORATIONS    2011-2018 poland\n";

 public static final String PROMPT = "box> ";

 public static final char OPT_INTEGER = 'I';
 public static final char OPT_STRING  = 'S';
 public static final char OPT_COLON   = ':';

 private static final int OP_MANDATORY = 0x01;
 private static final int OP_OPTIONAL  = 0x02;

 private static BufferedReader input;
 private static boolean running;
 
 public static String err_string;

 static {
  try {
   input=new BufferedReader(new InputStreamReader(System.in,"utf-8"));
   cl=new ConsoleIf();
  } catch(Throwable t) {}
 }

 public static void stop() throws Throwable {
  running=false;
  SrvConnection.get(0).close();
 }

 private static boolean is_option_type(char opt) {
  if ((opt==OPT_INTEGER)||(opt==OPT_STRING)) return true;
   else return false;
 }

 private static Integer parse_hex_value(String s) {
  Long res=null;

  try {
   res=Long.decode(s);
  } catch(Throwable t) {}

  if (res!=null) return new Integer(res.intValue());
   else return null;
 }

 private static Option parse_typed_option(char type,char opt,String arg,boolean for_option) {
  Option o=null;

  if (type==OPT_INTEGER) {
   Integer val=parse_hex_value(arg);

   if (val!=null) {
    if (for_option) {
     o=new Option(Option.OPT_INT_ARG,opt,val);       
    } else {
     o=new Option(Option.OPT_PURE_ARG,val);       
    }
   }

  } else

  if (type==OPT_STRING) {
   if (for_option) {
    o=new Option(Option.OPT_STR_ARG,opt,arg);       
   } else {
    o=new Option(Option.OPT_PURE_ARG,arg);
   }
  }

  return o;
 }

 private static int mandatory_args_cnt(Interpreter.CmdDesc desc) {
  int cnt=0;

  if (desc.no_options()) return cnt;

  int opt_idx=0;
  String options=desc.opt_string;

  while(opt_idx<options.length()) {
   char opt=options.charAt(opt_idx);

   if (is_option_type(opt)) {
    cnt++;
    opt_idx++;
   } else break;
  }

  return cnt;
 }

 private static Cmd parse_cmd(Interpreter.CmdDesc desc,String tokens[]) {
  Vector v=new Vector();
  Cmd res=null;

  String cmd_name=tokens[0];

  int tok_idx=1;
  int opt_idx=0;

  String options=desc.opt_string;

  if (options==null) {
   if (tokens.length>1) {
    err_string="unexpected arguments";
   }

   Cmd cmd=new Cmd(cmd_name,new Option[0],desc);
   return cmd;
  }

  int opts_len=options.length();

  int state=OP_MANDATORY;

  while(tok_idx<tokens.length) {
   String arg=tokens[tok_idx];

   switch(state) {
    case OP_MANDATORY:
     if (opt_idx>=opts_len) {
      err_string="too many arguments to command";
      return null;
     }

     if (arg.startsWith("-")) {
      state=OP_OPTIONAL;           
      continue;
     }

     char opt=options.charAt(opt_idx);

     if (is_option_type(opt)) {
      Option o=parse_typed_option(opt,(char)0,arg,false);
 
      if (o==null) {
       err_string="invalid integer "+arg;
       return null;
      }

      v.addElement(o);

      tok_idx++;
      opt_idx++;
     } else {
      state=OP_OPTIONAL;           
     }
     break;

    case OP_OPTIONAL:
     if (arg.charAt(0)!='-') {
      err_string="expecting option - argument (found '"+arg+"')";
      return null;
     }

     if (arg.length()!=2) {
      err_string="invalid option name '"+arg+"'";
      return null;
     }

     opt=arg.charAt(1);

     if (is_option_type(opt)) {
      err_string="unknown option '"+arg+"'";
      return null;            
     }

     opt_idx=options.indexOf(opt);

     if (opt_idx<0) {
      err_string="unknown option '"+arg+"'";
      return null;            
     } 

     if ((opt_idx+1)<opts_len) {
      char type=options.charAt(opt_idx+1);

      if (is_option_type(type)) {
       if ((tok_idx+1)>=tokens.length) {
        err_string="missing argument for option '"+arg+"'";
        return null;            
       }

       arg=tokens[tok_idx+1];

       Option o=parse_typed_option(type,opt,arg,true);
 
       if (o==null) {
        err_string="invalid integer "+arg;
        return null;
       }

       v.addElement(o);

       tok_idx+=2;
      } else {
       Option o=new Option(Option.OPT_NO_ARG,opt);
       v.addElement(o);
 
       tok_idx++;
      }
     } else {
      Option o=new Option(Option.OPT_NO_ARG,opt);
      v.addElement(o);
 
      tok_idx++;
     }
     break;
   }
  }
  
  Option opttab[]=new Option[v.size()];

  for(int i=0;i<v.size();i++) {
   opttab[i]=(Option)v.elementAt(i);
  }  

  Cmd cmd=new Cmd(cmd_name,opttab,desc);

  return cmd;
 }

 public static Option getopt(Option options[],char opt) {
  for(int i=0;i<options.length;i++) {
   Option o=options[i];
   if (o.option==opt) return o;
  }

  return null;
 }

 public static void report_error(String err) {
  System.err.println("error: "+err);
 }

 private static String read_line() throws Throwable {
  err_string=null;
  System.out.print(PROMPT);
  return input.readLine().trim();
 }

 private static String read_line(BufferedReader script) throws Throwable {
  err_string=null;
  System.out.print(PROMPT);

  String line=null;
  try {
   line=script.readLine().trim();
  } catch(Throwable t) {}

  return line;
 }

 private static String[] get_tokens(String line) {
  Vector v=new Vector();
  boolean quoted=false;
  int tbegin=-1;
  int tend=-1;
 
  for(int i=0;i<line.length();i++) {
   char c=line.charAt(i);

   if (quoted) {
    if (c=='"') {
     quoted=false;

     if (i<(line.length()-1)) {
      char next=line.charAt(i+1);
      if (!Character.isWhitespace(next)) {
       err_string="no whitespace after closing quote";
       return null;
      }
     }

     tend=i;
     String token=line.substring(tbegin+1,tend);
     v.addElement(token);
     tbegin=-1;
     tend=-1;
    }

   } else {
    if (c=='"') {
     if (tbegin!=-1) {
      err_string="unexpected quote inside a token";
      return null;
     }
     quoted=true;
     tbegin=i;
    } else

    if (!Character.isWhitespace(c)) {
     if (tbegin==-1) {
      tbegin=i;
     }
    } else {
     if (tbegin!=-1) {
      tend=i;
      String token=line.substring(tbegin,tend);
      v.addElement(token);
      tbegin=-1;
      tend=-1;
     }     
    }
   }
  }

  if (tbegin!=-1) {
   if (quoted) {
    err_string="no closing quote";
    return null;
   }

   tend=line.length();
   String token=line.substring(tbegin,tend);
      
   v.addElement(token);
   tbegin=-1;
   tend=-1;
  }

  String tokens[]=new String[v.size()];

  for(int i=0;i<v.size();i++) {
   tokens[i]=(String)v.elementAt(i);
  }  

  return tokens;
 }

 private static void parse_line(String line) throws Throwable {
  String tokens[]=get_tokens(line);

  if (tokens==null) {
   report_error(err_string);
   return;
  }

  if (tokens.length==0) return;

  Interpreter.CmdDesc desc=Interpreter.lookup_cmd(tokens[0]);

  if (desc==null) {
   report_error("unknown command");
   return;
  }

  if ((tokens.length>1)&&(desc.no_options())) {
   report_error("unexpected arguments to command");
   return;
  }

  Cmd cmd=parse_cmd(desc,tokens);

  if (cmd==null) {
   report_error(err_string);
   return;
  }

  if (!Interpreter.run_cmd(cl,cmd)) {
   report_error(err_string);
   return;
  }
 }

 public static void run() {
  try {
   System.out.println(BANNER);

   running=true;

   while(running) {
    String line=read_line();
    parse_line(line);
   }
  } catch(Throwable t) {
   t.printStackTrace();
  }

 }

 public static void run_script(ConsoleIf cl,String filename) {
  BufferedReader script=null;

  try {
   FileInputStream fis=new FileInputStream(filename);
   script=new BufferedReader(new InputStreamReader(fis,"utf-8"));
  } catch(Throwable t) {}

  if (script==null) {
   report_error("cannot open script file");
   return;
  }

  try {
   boolean script_running=true;

   while(script_running) {
    String line=read_line(script);
    if (line==null) break;

    cl.outputln(PROMPT+line);

    parse_line(line);
   }
  } catch(Throwable t) {
   t.printStackTrace();
  }

 }

 public static void main(String args[]) {
  try {
   run();
  } catch(Throwable t) {
   t.printStackTrace();
  }
 }
}
