/**
 * Copyright (c) 2007, Swedish Institute of Computer Science.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * This file is part of MSPSim.
 * -----------------------------------------------------------------
 *
 * GenericNode
 *
 * Author  : Joakim Eriksson
 */

package se.sics.mspsim.platform;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URISyntaxException;
import java.net.URL;
import javax.swing.JFrame;
import se.sics.mspsim.cli.CommandHandler;
import se.sics.mspsim.cli.DebugCommands;
import se.sics.mspsim.cli.FileCommands;
import se.sics.mspsim.cli.MiscCommands;
import se.sics.mspsim.cli.NetCommands;
import se.sics.mspsim.cli.ProfilerCommands;
import se.sics.mspsim.cli.StreamCommandHandler;
import se.sics.mspsim.cli.WindowCommands;
import se.sics.mspsim.core.Chip;
import se.sics.mspsim.core.EmulationException;
import se.sics.mspsim.core.MSP430;
import se.sics.mspsim.core.MSP430Config;
import se.sics.mspsim.core.MSP430Constants;
import se.sics.mspsim.extutil.highlight.HighlightSourceViewer;
import se.sics.mspsim.ui.ConsoleUI;
import se.sics.mspsim.ui.ControlUI;
import se.sics.mspsim.ui.JFrameWindowManager;
import se.sics.mspsim.ui.StackUI;
import se.sics.mspsim.ui.WindowUtils;
import se.sics.mspsim.util.ArgumentManager;
import se.sics.mspsim.util.ComponentRegistry;
import se.sics.mspsim.util.ConfigManager;
import se.sics.mspsim.util.ELF;
import se.sics.mspsim.util.IHexReader;
import se.sics.mspsim.util.MapTable;
import se.sics.mspsim.util.OperatingModeStatistics;
import se.sics.mspsim.util.PluginRepository;
import se.sics.mspsim.util.StatCommands;

public abstract class GenericNode extends Chip implements Runnable {

  private static final String PROMPT = "MSPSim>";

  protected final MSP430 cpu;
  protected final ComponentRegistry registry;
  protected ConfigManager config;

  protected String firmwareFile = null;
  protected ELF elf;
  protected OperatingModeStatistics stats;


  public GenericNode(String id, MSP430Config config) {
    super(id, new MSP430(0, new ComponentRegistry(), config));
    this.cpu = (MSP430)super.cpu;
    this.registry = cpu.getRegistry();
  }

  public ComponentRegistry getRegistry() {
    return registry;
  }

  public MSP430 getCPU() {
    return cpu;
  }

  public abstract void setupNode();

  public void setCommandHandler(CommandHandler handler) {
    registry.registerComponent("commandHandler", handler);
  }

  public void setupArgs(ArgumentManager config) throws IOException {
    String[] args = config.getArguments();
    if (args.length == 0) {
      System.err.println("Usage: " + getClass().getName() + " <firmware>");
      System.exit(1);
    }
    firmwareFile = args[0];
    if (!(new File(firmwareFile)).exists()) {
      System.err.println("Could not find the firmware file '" + firmwareFile + "'.");
      System.exit(1);
    }
    if (config.getProperty("nogui") == null) {
      config.setProperty("nogui", "false");
    }
    /* Ensure auto-run of a start script */
    if (config.getProperty("autorun") == null) {
      File fp = new File("scripts/autorun.sc");
      if (fp.exists()) {
        config.setProperty("autorun", "scripts/autorun.sc");
      } else {
        try {
          File dir = new File(GenericNode.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile();
          fp = new File(dir, "scripts/autorun.sc");
          if (fp.exists()) {
            config.setProperty("autorun", fp.getAbsolutePath());
          }
        } catch (URISyntaxException e) {
          // Failed to find auto run script
        }
      }
    }

    if (firmwareFile.endsWith("ihex")) {
      // IHEX Reading
      int[] memory = cpu.memory;
      IHexReader reader = new IHexReader();
      reader.readFile(memory, firmwareFile);
    } else {
      loadFirmware(firmwareFile);
    }
    config.setProperty("firmwareFile", firmwareFile);

    String mapFile = config.getProperty("map");
    if (mapFile != null) {
      MapTable map = new MapTable(mapFile);
      cpu.getDisAsm().setMap(map);
      cpu.setMap(map);
      registry.registerComponent("mapTable", map);
    }
    
    setup(config);


    if (!config.getPropertyAsBoolean("nogui", false)) {
      // Setup control and other UI components
      ControlUI control = new ControlUI();
      registry.registerComponent("controlgui", control);
      registry.registerComponent("stackchart", new StackUI(cpu));
      HighlightSourceViewer sourceViewer = new HighlightSourceViewer();
      // Add the firmware location to the search path
      File fp = new File(firmwareFile).getParentFile();
      if (fp != null) {
          try {
              // Get absolute path
              fp = fp.getCanonicalFile();
          } catch (Exception e) {
              // Ignore
          }
          sourceViewer.addSearchPath(fp);
      }
      control.setSourceViewer(sourceViewer);
    }

    String script = config.getProperty("autorun");
    if (script != null) {
      File fp = new File(script);
      if (fp.canRead()) {
        CommandHandler ch = registry.getComponent(CommandHandler.class, "commandHandler");
        script = script.replace('\\', '/');
        System.out.println("Autoloading script: " + script);
        config.setProperty("autoloadScript", script);
        if (ch != null) {
          ch.lineRead("source \"" + script + '"');
        }
      }
    }

    if (args.length > 1) {
        // Run the following arguments as commands
        CommandHandler ch = registry.getComponent(CommandHandler.class, "commandHandler");
        if (ch != null) {
            for (int i = 1; i < args.length; i++) {
                System.out.println("calling '" + args[i] + "'");
                ch.lineRead(args[i]);
            }
        }
    }
    System.out.println("-----------------------------------------------");
    System.out.println("MSPSim " + MSP430Constants.VERSION + " starting firmware: " + firmwareFile);
    System.out.println("-----------------------------------------------");
    System.out.print(PROMPT);
    System.out.flush();
  }

  public void setup(ConfigManager config) {
    this.config = config;

    registry.registerComponent("cpu", cpu);
    registry.registerComponent("node", this);
    registry.registerComponent("config", config);
    
    CommandHandler ch = registry.getComponent(CommandHandler.class, "commandHandler");

    if (ch == null) {
        if (config.getPropertyAsBoolean("jconsole", false)) {
            ConsoleUI console = new ConsoleUI();
            PrintStream consoleStream = new PrintStream(console.getOutputStream());
            ch = new CommandHandler(consoleStream, consoleStream);
            JFrame w = new JFrame("ConsoleUI");
            w.add(console);
            w.setBounds(20, 20, 520, 400);
            w.setLocationByPlatform(true);
            String key = "console";
            WindowUtils.restoreWindowBounds(key, w);
            WindowUtils.addSaveOnShutdown(key, w);
            w.setVisible(true);
            console.setCommandHandler(ch);
        } else {
            ch = new StreamCommandHandler(System.in, System.out, System.err, PROMPT);
        }
        registry.registerComponent("commandHandler", ch);
    }
    
    stats = new OperatingModeStatistics(cpu);
    
    registry.registerComponent("pluginRepository", new PluginRepository());
    registry.registerComponent("debugcmd", new DebugCommands());
    registry.registerComponent("misccmd", new MiscCommands());
    registry.registerComponent("filecmd", new FileCommands());
    registry.registerComponent("statcmd", new StatCommands(cpu, stats));
    registry.registerComponent("wincmd", new WindowCommands());
    registry.registerComponent("profilecmd", new ProfilerCommands());
    registry.registerComponent("netcmd", new NetCommands());
    registry.registerComponent("windowManager", new JFrameWindowManager());

    // Monitor execution
    cpu.setMonitorExec(true);
    
    setupNode();

    registry.start();

    cpu.reset();
  }
  
 
  public void run() {
    if (!cpu.isRunning()) {
      try {
        cpu.cpuloop(); 
      } catch (Exception e) {
        /* what should we do here */
        e.printStackTrace();
      }
    }
  }
  
  public void start() {
    if (!cpu.isRunning()) {
      Thread thread = new Thread(this);
      // Set this thread to normal priority in case the start method was called
      // from the higher priority AWT thread.
      thread.setPriority(Thread.NORM_PRIORITY);
      thread.start();
    }
  }
  
  public void stop() {
    cpu.stop();
  }

  public void step() throws EmulationException {
    step(1);
  }

  // A step that will break out of breakpoints!
  public void step(int nr) throws EmulationException {
    if (!cpu.isRunning()) {
      cpu.stepInstructions(nr);
    }
  }

  public ELF loadFirmware(URL url) throws IOException {
      return loadFirmware(url, cpu.memory);
  }

  @Deprecated public ELF loadFirmware(URL url, int[] memory) throws IOException {
    DataInputStream inputStream = new DataInputStream(url.openStream());
    ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
    byte[] firmwareData = new byte[2048];
    int read;
    while ((read = inputStream.read(firmwareData)) != -1) {
      byteStream.write(firmwareData, 0, read);
    }
    inputStream.close();
    ELF elf = new ELF(byteStream.toByteArray());
    elf.readAll();
    return loadFirmware(elf, memory);
  }

  public ELF loadFirmware(String name) throws IOException {
      return loadFirmware(name, cpu.memory);
  }

  @Deprecated public ELF loadFirmware(String name, int[] memory) throws IOException {
    return loadFirmware(ELF.readELF(firmwareFile = name), memory);
  }

  public ELF loadFirmware(ELF elf) {
      return loadFirmware(elf, cpu.memory);
  }

  @Deprecated public ELF loadFirmware(ELF elf, int[] memory) {
    if (cpu.isRunning()) {
        stop();
    }
    this.elf = elf;
    elf.loadPrograms(memory);
    MapTable map = elf.getMap();
    cpu.getDisAsm().setMap(map);
    cpu.setMap(map);
    registry.registerComponent("elf", elf);
    registry.registerComponent("mapTable", map);
    return elf;
  }

  public int getConfiguration(int param) {
      return 0;
  }
}
