package editor;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JOptionPane;

import parser.Token;

public class Mack
	{
	//
	// Constants
	//
	static final int  MAX_SECTION_SIZE	= 2000;
  private Map<String, Integer>  	constants = new HashMap<String, Integer>();
  private Map<String, Integer>  	variables = new HashMap<String, Integer>();
  private Map<String, Integer>  	varSizes  = new HashMap<String, Integer>();
  private Map<String, Integer>  	labels 		= new HashMap<String, Integer>();
  private Map<String, int[]> 	    outstandingLabels = new HashMap<String, int[]>();
  
  private int											bssSize 				= 0;
  private byte[] 									dataSection 		= new byte[MAX_SECTION_SIZE];
  private int											dataSectionSize = 0;
  private byte[] 									codeSection 		= new byte[MAX_SECTION_SIZE];
  private int											codeSectionSize = 0;
  private	int  										oldCodeSize = -1; // Used for debugging
  public  StringBuilder						objCode = new StringBuilder( 1000 );
  private int                     stackDepth = 0;
  private int                     loadSize = 0;     // A global parser variable.

  //
  // Constructor
  //
  Mack()
	  {
	  }
  
  //
  // Methods
  //
  public int	parseInt( Token  number )
	  {
	  String  s = number.image;
	  if( s.startsWith("0x") )
	  	return (int)Long.parseLong( s.substring(2), 16 ); // Hex literal
	  return Integer.parseInt( s ); //Decimal literal
	  }
  
  
  public void  defConst( Token  name,  int  value )  throws Error
  	{
	  if( constants.get(name.image) == null )
	  	constants.put( name.image, value );
	  else
	  	throw new Error( "\"" + name.image + "\" has already been defined to be " + constants.get(name.image) );
  	}
  
  
 	public int  ref( Token  ident )  throws Error
  	{
  	// Is it a constant?
  	Integer  value = constants.get( ident.image );
  	if( value != null  )
    	return value;

    value = variables.get( ident.image );
    if( value != null  )
      {
      if( loadSize != 0 )
        throw new Error( "Cannot reference multiple variables in a single operand." );
      loadSize = varSizes.get( ident.image );
      return value;
      }
    
    value = labels.get( ident.image );
    if( value != null  )
      return value;

    throw new Error( "Identifier \"" + ident.image + "\" is undefined" );
  	}

  
  public void resetLoadSize()
    {
    loadSize = 0;
    }
 	
  
 	public void  defBss( Token  name, int  size, int  repeat )
		{
		if( dataSectionSize > 0 )
	  	throw new Error( "All BSS data (like " + name.image + ") must be defined BEFORE any data definitions." );

		if( name != null )
			{
			// Register the address and size of this "named" data (also known as a "variable").
		  if( variables.get(name.image) == null )
		  	{
		  	variables.put( name.image, bssSize+dataSectionSize );
		  	varSizes .put( name.image, size );
		  	}
		  else
		  	throw new Error( "\"" + name.image + "\" has already been defined at offset " + variables.get(name.image) );
			}
		
		// For BSS variables, just allocate space for it.
		bssSize += ( size * repeat );
		}
	
 	
 	public void  defData( Token  name, int  size, int  initValue, int  repeat )
		{
		if( name != null )
			{
			// Register the address and size of this "named" data (also known as a "variable").
		  if( variables.get(name.image) == null )
		    {
		  	variables.put( name.image, bssSize+dataSectionSize );
        varSizes .put( name.image, size );
        }
		  else
		  	throw new Error( "\"" + name.image + "\" has already been defined at offset " + variables.get(name.image) );
			}
		
	  // Set the bytes in the data section
	  for( int i=0; i<repeat; i++ )
	  	{
	  	if( size == 1 )
	  		dataSection[dataSectionSize++] = (byte) initValue;
	  	else if( size ==2 )
	  		{
	  		dataSection[dataSectionSize++] = (byte)( (initValue>>8) & 0xFF );
	  		dataSection[dataSectionSize++] = (byte)(  initValue     & 0xFF );
	  		}
	  	else // size==4
	  		{
	  		dataSection[dataSectionSize++] = (byte)( (initValue>>24) & 0xFF );
	  		dataSection[dataSectionSize++] = (byte)( (initValue>>16) & 0xFF );
	  		dataSection[dataSectionSize++] = (byte)( (initValue>>8 ) & 0xFF );
	  		dataSection[dataSectionSize++] = (byte)(  initValue      & 0xFF );
	  		}
	  	}
		}

 	
  @SuppressWarnings("fallthrough")
  public void  defString( Token  name, Token  strTok )
    {
    String  beforeStr = strTok.image;
    int     addr = bssSize + dataSectionSize;
    
    // Loop thru each raw string char (not include the quotes at the ends.
    int  state = 1; // Normal state
    int  val = 0;
    for( int i=1; i < beforeStr.length()-1; i++ )
      {
      int  c =  beforeStr.charAt( i );
      switch( state )
        {
        case 0: // Back-slash Digit seen.
          if( c >= '0'  && c <= '9' )
            {
            val = ( val * 10 ) + ( c - '0' );
            break;
            }
          dataSection[dataSectionSize++] = (byte) val;
          // Fall-thru to handle next character
          
        case 1: // Normal
          if( c == '\\' )
            state = 2; // Back-slash seen
          else
            dataSection[dataSectionSize++] = (byte) c;
          break;
          
        case 2: // Back-slash seen
          if( c >= '0'  && c <= '9' )
            {
            val = ( c - '0' );
            state = 0; // Back-slash Digit seen.
            }
          if( c == '\n' )
            state = 1;  // Normal - Ignore Black-slash New-Line
          else if(  c == '\r' )
            state = 3;  // Back-slash n|r seen
          else
            {
            switch( c )
              {
              // Standard C/C++/Java Back-slash codes
              case 'n' : c = '\n'; break;
              case 't' : c = '\t'; break;
              case 'r' : c = '\r'; break;
              case 'f' : c = '\f'; break;
              case '\\': c = '\\'; break;
              case '\'': c = '\''; break;
              
              // Special Stock-Ticker codes.
              // Print Modes:
              case 'O' : c = '\25'; break;  // PRINT_MODE_OR
              case 'X' : c = '\26'; break;  // PRINT_MODE_XOR
              case 'R' : c = '\27'; break;  // PRINT_MODE_REPLACE
              // Wrap Modes:
              case 'S' : c = '\20'; break;  // VSCROLL
              case 'w' : c = '\21'; break;  // WRAP_1LINE
              case 'W' : c = '\22'; break;  // WRAP_2LINES
              case 'h' : c = '\23'; break;  // HSCOLL_1LINE
              case 'H' : c = '\24'; break;  // HSCOLL_2LINES
              // Cursor movement
              case 'b' : c = '\31'; break;  // Backspace
              }
            dataSection[dataSectionSize++] = (byte) c;
            }
          break;
         
        case 3: // Back-slash r seen
          if( c == '\n' )
            state = 1; // Normal - Ignore Black-slash CR New-Line
          else if( c == '\\' )
            state = 2; // Back-slash seen
          else
            dataSection[dataSectionSize++] = (byte) c;
          break;
        }
      }
    
    // If the last char is back-slash digit, output the character.
    if( state == 0 )
      dataSection[dataSectionSize++] = (byte) val;
    
    // Null terminal all strings.
    dataSection[dataSectionSize++] = 0;
    
    System.out.println( "strTok= \"" + strTok.image + "\"" );
    if( name != null )
      {
      // Register the address of the named string.
      if( variables.get(name.image) == null )
        {
        variables.put( name.image, addr );
        varSizes .put( name.image, (bssSize + dataSectionSize) - addr );
        }
      else
        throw new Error( "String \"" + name.image + "\" has already been defined at offset " + variables.get(name.image) );
      }
    
    }
  
  
 	public void  defLabel( Token  label )
		{
		if( label != null )
			{
			// Register the address of this "label".
      if( constants.get(label.image) != null )
        throw new Error( "Constant \"" + label.image + "\" is being usage as a label." );
      if( variables.get(label.image) != null )
        throw new Error( "Variable \"" + label.image + "\" is being usage as a label." );
      if( labels.get(label.image) != null )
        throw new Error( "Label \"" + label.image + "\" has already been defined at offset " + labels.get(label.image) );
      
		  labels.put( label.image, codeSectionSize );
	 		dumpObjectCode( label.image + ":", -999 );
			}
		}
	
 	
 	public void genOp( int  opcode, Token  token, int  stackDelta )
 		{
 		codeSection[codeSectionSize++] = (byte)opcode;
 		dumpObjectCode( token.image + "\t line: " + token.beginLine, stackDelta );
 		}
 	
 	/**
 	 * Returns the minimum number of bytes a "Push" instruction needs
 	 * to push the constant "n".
 	 * @param n - constant to be pushed.
 	 * @return Size of "push" instruction.
 	 */
 	public int pushSize( int  n )
 	  {
 	  // Return the size of a push instruction for a given "n".
    if( n >= 0  &&  n <= 63 )
      return 1;
    if( n >= -7680  &&  n <= 8191 )
      return 2;
    if( n >= -32768  &&  n <= 32767 )
      return 3;
    return 5;
 	  }
 	
 	
 	private void  genPush( int  n,  int  opCodeSize, int address )
 	  {
    if( opCodeSize == 1 )
      // _0_n_n_n_n_n_n_n_  
      codeSection[address] = (byte)n;
    else if( opCodeSize == 2 )
      {
      // _1_0_n_n_n_n_n_n_  nnnnnnn 
      codeSection[address  ] = (byte)( 0x80 | ((n >> 8) & 0x3F) );
      codeSection[address+1] = (byte)( n & 0xFF );
      }
    else if( opCodeSize == 3 )
      {
      // _1_0_1_0_0_0_0_0_  nnnnnnn  nnnnnnn  
      codeSection[codeSectionSize++] = (byte) 0xA0;
      codeSection[address+1] = (byte)( (n >> 8) & 0xFF );
      codeSection[address+2] = (byte)( n & 0xFF );
      }
    else
      {
      // _1_0_1_0_0_0_0_1_  nnnnnnn  nnnnnnn  nnnnnnn  nnnnnnn    
      codeSection[address  ] = (byte) 0xA1;
      codeSection[address+1] = (byte)( (n >> 24) & 0xFF );
      codeSection[address+2] = (byte)( (n >> 16) & 0xFF );
      codeSection[address+3] = (byte)( (n >>  8) & 0xFF );
      codeSection[address+4] = (byte)( n & 0xFF );
      }
 	  }
 	
 	/**
 	 * Generate the smallest Push instruction possible given the size of n.
 	 * @param n
 	 */
 	public void  genPush( int  n )
 		{
 		int  size = pushSize( n );
 		genPush( n , size, codeSectionSize );
 		codeSectionSize += size;
 		
 		dumpObjectCode( "Push " + n + " (0x" + Long.toHexString(n) + ")", +1 );
 		}
 	

  /**
   * When an undefined label needs to be pushed, call this to have is back-packed later.
   * When pushing a relative address, it is assumed that a branch instruction follows and
   * the base address is one byte past the last byte generated here.
   * This will save the address of the generated push instruction and push the following values.
   *   Source Code         Fars Max Dist    Push Absolute    Push Relative
   *   -----------------   ---- --------    ---------------  ---------
   *   ->label             0    63 bytes    0x00             0x01
   *   ->label.far         1    8191 bytes  0x8000           0x8001
   *   ->label.far.far     2    32767 bytes 0xA00000         0xA00001
   *   ->label.far.far.far 3    2^31 bytes  0xA100000000     0xA100000001
   * 
   * @param labelName
   * @param relative - true to push a relative address, false to push an absolute address.
   * @param howFar   - How far away the label is expected to be.
   */
  private void genPushBackPatchLable( String  labelName, boolean  relative, int  howFar )
    {
    if( howFar < 0  &&  howFar > 3 )
      throw new Error( "Label reference of \"" + labelName + "\" has too many \".far\" prefixes." );
    
    // Make a place to save the current address for back-patching.
    int[]  patchAddrs = outstandingLabels.get( labelName );
    if( patchAddrs == null )
      patchAddrs = new int[1];
    else
      {
      // Make the array 1 larger.  This sounds slow, but the sizes
      // are always small.
      int[]  copy = new int[patchAddrs.length+1];
      for( int i=0; i<patchAddrs.length; i++ )
        copy[i] = patchAddrs[i];
      patchAddrs = copy;
      }
    
    // Remember the address of "push" instruction.
    patchAddrs[patchAddrs.length-1] = codeSectionSize;
    outstandingLabels.put( labelName, patchAddrs );

    // Generate the 1, 2, 3 or 5-byte "push" instruction.
    // Push a "1" for relative or "0" for absolute. The back-patcher will read this.
    int  size = ( howFar==3 ? 5 : howFar+1 );
    genPush( (relative ? 1 : 0), size, codeSectionSize );
    codeSectionSize += size;

    dumpObjectCode( "Push forward ref to: " + labelName, +1 );
    }
  
  
  public void  backPatch()
    {
    int  savedCodeSectionSize = codeSectionSize;
    if( outstandingLabels.size() == 0 )
      return;
    
    objCode.append( "\nBack Paches:\n" );
    
    // Loop thru all the labels
    for( String labelName : outstandingLabels.keySet() )
      {
      int[]  places = outstandingLabels.get(labelName);
      if( places != null )
        {
        Integer  labelAddr = labels.get( labelName );
        if( labelAddr == null )
          throw new Error( "Label  \"" + labelName + "\" was never defined." );
        
        // Loop thru all the references to this label.
        for( int i=0; i<places.length; i++ )
          {
          int  address = places[i]; 
          int  firstByte = codeSection[address] & 0xFF; // Convert an "unsigned byte" -> "int"
          int  size = ( firstByte==0x80 ? 2 : (firstByte==0xA0 ? 3 : (firstByte==0xA1 ? 5 : 1)) );
          
          // If pushed value is 0x01 then n = Relative Address, else n= Absolute Address.
          int   n = codeSection[address+size-1]==0x01 ? labelAddr-(address+size+1) : labelAddr;
          
          // Did the user give us enough space to store n?
          if( size < pushSize(n) )
            {
            for( int far=1; far<pushSize(n) && far<=3; far++ ) 
              labelName += ".far";
            throw new Error( "Label too far forward. Reference #" + (i+1) + " should be \"" + labelName + "\"." );
            }
          
          // Regenerate the Push instruction.
          genPush( n, size, address );
          
          // Dump it (using a hack).
          oldCodeSize     = address;
          codeSectionSize = address + size;
          dumpObjectCode( "Backpatck ref #" + (i+1) + " of \"" + labelName + "\" with a " +
                          size + "-byte Push " + n + " (" + Long.toHexString(n) + ")", 0 );
          oldCodeSize     = savedCodeSectionSize;
          codeSectionSize = savedCodeSectionSize;
          }
        }
      }
    }
  
  
  /**
   * Push the absolute address of a label.
   * @param labTok
   * @param howFar - How far away the label is expected to be.
   */
  public void  genPushLabel( Token  labTok, int  howFar )
    {
    Integer  labValue = labels.get( labTok.image );
    if( labValue != null  )
      // Push the know address.
      genPush( labValue );
    else
      // Push an unknown address that will be fixed later.
      genPushBackPatchLable( labTok.image, false, howFar );
    }
  
    /**
     * Generate a branch or call instruction giving an optional label.
     * @param opcode - The opcode of the actual instruction
     * @param token  - The branch or call token.
     * @param labTok - null or the label to branch to.
     * @param howFar - How far away the label is expected to be.
     */
   	public void  genBranch( int  opcode, Token  token, Token  labTok, int  howFar )
 	  {
 	  final int  SIZE_OF_BRANCH = 1;
 	  // If a label was specified, then push the relative offset to it
 	  if( labTok != null )
 	    {
      Integer  labValue = labels.get( labTok.image );
      if( labValue != null  )
        {
        // Label is defined.
        // Push: label_address - address_of_instr_after_branch
        //     = label_address - ( current + sizeOfPush + SIZE_OF_BRANCH )
        int  offset = labValue - codeSectionSize - SIZE_OF_BRANCH;
        int  sizeOfPush = 2;
        while( sizeOfPush != pushSize(offset - sizeOfPush) )
          sizeOfPush = pushSize( offset - sizeOfPush );
        
        // Push the know offset.
        genPush( offset - sizeOfPush );
        }
      else
        {
        // Push an unknown offset that will be fixed later. (assume 1 byte follows)
        genPushBackPatchLable( labTok.image, true, howFar );
        }
 	    }
    int  stackDelta = (opcode==0xD0 ? -2 : (opcode==0xD7||opcode==0xDF ? -1 : (opcode==0xD8 ? -999 : -3)) );
 	  // Generate the branch instruction.  (Do not generate anything else first)
    codeSection[codeSectionSize++] = (byte)opcode;
    if( labTok == null )
      dumpObjectCode( token.image + " to PC+TOS\t line: " + token.beginLine, stackDelta );
    else if( labels.get(labTok.image) == null )
      dumpObjectCode( token.image + " ->" + labTok.image + " (undef)\t line: " + token.beginLine, stackDelta );
    else
      dumpObjectCode( token.image + " ->" + labTok.image + " (" + Long.toHexString(labels.get(labTok.image)) + ")\t line: " + token.beginLine, stackDelta );
 	  }
 	
 	
  public void genPushOrLoad( int  n )
    {
    // Push n even if it is an address.
    genPush( n );
    
    if( loadSize > 0 )
      {
      // "n" is an address, so Generate a load
      if( loadSize!=1  &&  loadSize!=2  &&  loadSize!=4 )
        throw new Error( "Cannot load " + loadSize + " bytes from address " + n + " implicitly." );
      
      // Generate a Load
      codeSection[codeSectionSize++] = (byte)( 0xE0 | (loadSize==4 ? 3 : loadSize) );
      dumpObjectCode( "Operand " + loadSize + "-byte Load.", 0 );
      }
    }
 	
 	private void  dumpObjectCode( String  comment, int  stackDelta )
 		{
 		if( oldCodeSize == -1 )
 			{
 			objCode.append( "Generated Object Code:\n" );
 			oldCodeSize = 0;
 			}
 		
 		if( stackDelta == -999 )
      objCode.append( "" + stackDepth + " \t" );
 		else
 		  objCode.append( "" + stackDepth + (stackDelta>=0 ? "+" : "") + stackDelta + "\t" );
 		stackDepth += stackDelta;
 		if( stackDepth < 0 )
 		 stackDepth = 0;
 		
 		objCode.append( Long.toHexString(oldCodeSize) + "\t" );

		// Loop thru all the bytes we have not displayed yet.
 		while( codeSectionSize > oldCodeSize )
 			{
 			String hexDigits = "00" + Long.toHexString( codeSection[oldCodeSize++] & 0xFF ).toUpperCase();
 			objCode.append( hexDigits.substring(hexDigits.length()-2) );
 			}
 		
 		if( comment == null )
      objCode.append( "\n" );
 		else
 		  objCode.append( "\t// " + comment + "\n" );
  	}
 	
 	
	public void  write( File  file )
  	{
  	try
			{
			// Open and clear the file.
	  	RandomAccessFile   fd = new RandomAccessFile( file, "rw" );
	  	fd.seek( 0 );
	  	fd.setLength( 0 );
	  	
	  	// Write the BSS Section
	  	fd.writeInt( 0x425353 ); 						// "\0BSS" - BSS Section Header
	  	fd.writeInt( 4 );  									// The size of this section is just a
	  	fd.writeInt( bssSize ); 						// 4-byte int (size of BSS).

	  	// Write the Data Section
	  	fd.writeInt( 0x44415441 ); 					// "DATA" - Data Section Header
	  	fd.writeInt( dataSectionSize );  		// L.E. 4-bytes Section Size
	  	fd.write( dataSection, 0, dataSectionSize ); // Write the bytes
	
	  	// Write the Code Section
	  	fd.writeInt( 0x434F4445 ); 					// "CODE" - Code Section Header 
	  	fd.writeInt( codeSectionSize  );  	// L.E. 4-bytes Section Size
	  	fd.write( codeSection, 0, codeSectionSize ); // Write the bytes
	  	
	  	// Close the file.
	  	fd.close();
			}
		catch( FileNotFoundException e )
			{
			if( file != null )
				JOptionPane.showMessageDialog( MiniTextEditor.frame, "File not found: " + file.getName() );
			}
		catch( IOException e )
			{
			if( file != null )
				JOptionPane.showMessageDialog( MiniTextEditor.frame, "File write error: " + file.getName() );
			}
  	}

  
	public void dump()
		{
		System.out.println( "\n\n-------------------------------------" );
		System.out.println( "Constant Table:" );
		for( String name : constants.keySet() )
			System.out.println( "  " + name + "\t = " + constants.get(name) );
		System.out.println( "\nVariable Table:" );
		for( String name : variables.keySet() )
			System.out.println( "  " + name + "\t = " + variables.get(name) + " (" + varSizes.get(name) + " bytes)");
    System.out.println( "Lable Table:" );
    for( String name : labels.keySet() )
      System.out.println( "  " + name + "\t = " + labels.get(name) );
    if( outstandingLabels.size() > 0 )
      {
      System.out.println( "Outstanding Lable Table:" );
      for( String name : outstandingLabels.keySet() )
        {
        int[]  places = outstandingLabels.get(name);
        System.out.println( "  \"" + name + "\" was referenced at:" );
        for( int i=0; i<places.length; i++ )
          System.out.println( "    " + i + "\t" + Long.toHexString(places[i]) );
        }
      }
		}
  
	}
