/**********************************************************************
This file is part of Crack dot Com's free source code release of Golgotha.
for information about compiling & licensing issues visit this URL
 If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/

#include "window/wmanager.hh"
#include "app/app.hh"
#include "time/profile.hh"
#include "app/registry.hh"
#include "threads/threads.hh"

i4_profile_class pf_app_calc_model("app::calc_model");
i4_profile_class pf_app_refresh("app::refresh");
i4_profile_class pf_app_get_input("app::get_input");

i4_application_class *i4_current_app=0;

i4_parent_window_class *i4_application_class::get_root_window()
{
  return wm;
}

i4_graphical_style_class *i4_application_class::get_style()
{
  if (wm)
    return wm->get_style();
  else return 0;
}

void i4_application_class::receive_event(i4_event *ev)
{
  if (ev->type()==i4_event::DISPLAY_CLOSE)
    finished=i4_T;
}

void i4_application_class::run()
{  
  i4_current_app=this;
  init();
  finished=i4_F; 
  int restore_priority=0;

  do
  {
    pf_app_calc_model.start();
    calc_model();    
    pf_app_calc_model.stop();

    pf_app_refresh.start();
    refresh();
    pf_app_refresh.stop();

    if (restore_priority)
    {
      i4_set_thread_priority(i4_get_thread_id(), I4_THREAD_PRIORITY_NORMAL);
      restore_priority=0;
    }

    pf_app_get_input.start();
    int repeat;
    i4_kernel.events_sent=0;
    do 
    {
      repeat=0;
      get_input();
      if (!finished)
      {
        if (display->display_busy() || 
            (idle() && i4_kernel.events_sent==0 && 
             !wm->need_redraw() &&
             display->get_context()->both_dirty->empty()))
        {
          repeat=1;
          if (!restore_priority)
          {
            restore_priority=1;
            i4_set_thread_priority(i4_get_thread_id(), I4_THREAD_PRIORITY_LOW);
          }
          i4_thread_yield();
        }
      }
    }
    while (repeat);

    pf_app_get_input.stop();


  } while (!finished);
  uninit();
  i4_current_app=0;
}


void i4_application_class::calc_model()
{}

// get_input polls the hardware for changes and reports them as events to event_handlers
void i4_application_class::get_input()
{
  i4_kernel.process_events();

}

void i4_application_class::refresh()
{
  wm->root_draw();
}


// if an exact match is not found the closest width and height are return
i4_display_class::mode *i4_application_class::find_mode(w16 &width, w16 &height, int driver_id)
{
  sw32 closest_dist=0xfffffff;
  w16 closest_width=0,
      closest_height=0;

  i4_display_class::mode *use=display->get_first_mode(driver_id);
  if (!use)
    return 0;

  do
  {

    // if we are opening a window and we can set the xres and yres, 
    //   then make them fit as best we can to the suggested width and height

    if (use->flags & i4_display_class::mode::RESOLUTION_DETERMINED_ON_OPEN)
    {
      use->xres=width;
      use->yres=height;
    }
      

    if (width==use->xres &&     // did we find a matching mode?
        height==use->yres)
      return use;


    // see how far off we are
    sw32 dist=((sw32)use->xres-(sw32)width)*((sw32)use->xres-(sw32)width)+
              ((sw32)use->yres-(sw32)height)*((sw32)use->yres-(sw32)height);
    if (distxres;
      closest_height=use->yres;
    }

    use=display->get_next_mode();
  } while (use);

  width=closest_width;
  height=closest_height;

  return 0;
}

i4_application_class::~i4_application_class()
{

}

void i4_application_class::memory_init()
{
  i4_init();
}


void i4_application_class::resource_init(char *resource_file, 
                                         void *resource_buffer)
{
  i4_string_man.load("resource/i4.res");

  if (resource_buffer)
    i4_string_man.load_buffer(resource_buffer,
                              "internal_buffer");
  else
    i4_string_man.load(resource_file);
}


void i4_application_class::handle_no_displays()
{
  i4_error("Could not find a display!");
}


static char *i4_display_key="SOFTWARE\\Crack dot Com\\I4\\1.0";

i4_bool i4_application_class::get_display_name(char *name, int max_len)
{
  return i4_get_registry(I4_REGISTRY_MACHINE, i4_display_key, "display", name, max_len);
}

void i4_application_class::display_init()
{
  char name[256];

  i4_display_list_struct *d, *found=0;

  if (get_display_name(name, 256))
  {
    for (d=i4_display_list; d; d=d->next)
      if (strcmp(d->name, name)==0)
        found=d;
  }
  
  if (!found)
    found=i4_display_list;

  if (!found)
    handle_no_displays();
    

  w16 width=640, height=480;

  // first try to find mode specified in the resource file
  i4_const_str xres=i4gets("default_xres", i4_F);
  i4_const_str yres=i4gets("default_yres", i4_F);
  
  if (!xres.null() && !yres.null())
  {
    i4_const_str::iterator xres_str=xres.begin(),
      yres_str=yres.begin();

    width=xres_str.read_number();
    height=yres_str.read_number();
  }

  w16 found_width=width,
      found_height=height;

  display=found->display;

  i4_display_class::mode *use=find_mode(found_width, found_height, found->driver_id);
  if (!use)
  {
    i4_warning("Unable to find an exact match for mode %dx%d, using %dx%d instead\n",
        width,height,
        found_width,found_height);

    use=find_mode(found_width, found_height, found->driver_id);

    if (!use)
      i4_error("What? Could not find that mode either");
  }

  if (!display->initialize_mode())
    handle_no_displays();

  wm=new i4_window_manager_class();
  wm->prepare_for_mode(display, use);

  i4_kernel.request_events(this,i4_device_class::FLAG_DISPLAY_CLOSE);
}



void i4_application_class::init()
{
  memory_init();
  resource_init("resource.res",0);
  display_init();
}

void i4_application_class::uninit()
{
  display_uninit();

  i4_uninit();
}

void i4_application_class::display_uninit()
{
  i4_kernel.process_events();
  delete wm;
  wm=0;
  
  i4_kernel.unrequest_events(this, i4_device_class::FLAG_DISPLAY_CLOSE);
  display->close();
  display=0;
}