/*
 *  Copyright 2014 Embedded Artists AB
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */


#include "mbed.h"
#include "AppImageViewer.h"
#include "lpc_swim_font.h"
#include "lpc_swim_image.h"
#include "Image.h"

/******************************************************************************
 * Defines and typedefs
 *****************************************************************************/
 
#define BTN_OFF    20
 
/******************************************************************************
 * Global variables
 *****************************************************************************/

/******************************************************************************
 * Private functions
 *****************************************************************************/

static void buttonClicked(uint32_t x)
{
  bool* done = (bool*)x;
  *done = true;
}

void AppImageViewer::draw()
{
    // Prepare fullscreen
    swim_window_open(_win, 
                   _disp->width(), _disp->height(),         // full size
                   (COLOR_T*)_fb1,
                   0,0,_disp->width()-1, _disp->height()-1, // window position and size
                   0,                                       // border
                   BLACK, BLACK, BLACK);                    // colors: pen, backgr, forgr

    // Create (but don't show) the button
    _btn = new ImageButton(_win->fb, _win->xpmax - BTN_OFF - _resOk->width(), _win->ypmax - BTN_OFF - _resOk->height(), _resOk->width(), _resOk->height());
    _btn->loadImages(_resOk);
    // Copy everything onto the back buffer
    memcpy(_fb2, _fb1, _disp->fbSize());
}

void AppImageViewer::load(const char* file)
{
    Image::ImageData_t pre = {0};
    
    int res = Image::decode(file, Image::RES_16BIT, &pre);
    if (res == 0) {
        DMBoard::instance().logger()->printf("[ImageLoader] Preparing %s\n", file);
        Image::ImageData_t* data = _mailbox.alloc(osWaitForever);        
        if (data != NULL) {
            *data = pre;
            _mailbox.put(data);
        } else {
            DMBoard::instance().logger()->printf("[ImageLoader] Failed to get memory to prepare %s\n", file);
        }
    }
}

static bool recursiveProcessFS(char* buff, const char* name, unsigned int maxLen, AppImageViewer* app, int depth, int maxDepth)
{
  if (depth > maxDepth) {
    return true;
  }
  uint32_t len = strlen(buff);
  if (len > 0) {
    if (buff[len - 1] != '/') {
      buff[len++] = '/';
      buff[len] = '\0';
    }
  }
  if ((strlen(name) + len) >= maxLen) {
    // avoid memory overwrite due to too long file path
    return true;
  }
  strcat(buff, name);
  len += strlen(name);

  DIR *d = opendir(buff);
  bool result = true; // success
  if (d != NULL) {
    struct dirent *p;
    while (result && ((p = readdir(d)) != NULL)) {
      result = recursiveProcessFS(buff, p->d_name, maxLen, app, depth+1, maxDepth);
      buff[len] = '\0';
    }
    closedir(d);
  } else {
    // a file
    if (len > 3) {
      if ((strncasecmp(buff+len-4, ".bmp", 4)==0) ||
          (strncasecmp(buff+len-4, ".png", 4)==0) ||
          (strncasecmp(buff+len-4, ".raw", 4)==0)) {
        DMBoard::instance().logger()->printf("[ImageLoader] found %s\n", buff);
        app->load(buff);
      }
    }
  }
  return result;
}

static void loaderTask(void const* args)
{
  char* buff = (char*)malloc(512);
  if (buff != NULL)
  {
    DMBoard::instance().logger()->printf("Recursive list of file and folders in /mci/\n");
    buff[0] = '\0';
    recursiveProcessFS(buff, "/mci/", 512, (AppImageViewer*)args, 0, 2);
    DMBoard::instance().logger()->printf("Recursive list of file and folders in /usb/\n");
    buff[0] = '\0';
    recursiveProcessFS(buff, "/usb/", 512, (AppImageViewer*)args, 0, 2);
    if (DMBoard::instance().display()->width() == 480) {
      DMBoard::instance().logger()->printf("Recursive list of file and folders in /qspi/480x272/\n");
      buff[0] = '\0';
      recursiveProcessFS(buff, "/qspi/480x272/", 512, (AppImageViewer*)args, 0, 1);
    } else {
      DMBoard::instance().logger()->printf("Recursive list of file and folders in /qspi/800x480/\n");
      buff[0] = '\0';
      recursiveProcessFS(buff, "/qspi/800x480/", 512, (AppImageViewer*)args, 0, 1);
    }
    free(buff);
  }
  DMBoard::instance().logger()->printf("loaderTask done\n");
}

/******************************************************************************
 * Public functions
 *****************************************************************************/

AppImageViewer::AppImageViewer() : _disp(NULL), _win(NULL), 
   _fb1(NULL), _fb2(NULL), _btn(NULL), _active(0), _resOk(NULL)
{
}

AppImageViewer::~AppImageViewer()
{
    teardown();
}

bool AppImageViewer::setup()
{
    _disp = DMBoard::instance().display();
    _win = (SWIM_WINDOW_T*)malloc(sizeof(SWIM_WINDOW_T));
    _fb1 = _disp->allocateFramebuffer();
    _fb2 = _disp->allocateFramebuffer();

    return (_win != NULL && _fb1 != NULL && _fb2 != NULL);
}

void AppImageViewer::runToCompletion()
{
    // Alternative 1: use the calling thread's context to run in
    bool done = false;
    draw();
    _btn->setAction(buttonClicked, (uint32_t)&done);
    void* oldFB = _disp->swapFramebuffer(_fb1);

    _active = 1;
    
    Thread* tLoader = new Thread(loaderTask, this, osPriorityNormal, 8192);
    
    bool first = true;
    Timer t;
    while(!done) {
      osEvent evt = _mailbox.get(1000);
      if (evt.status == osEventMail) {
        COLOR_T* fb;
        if (_active == 1) {
          // render on the second frame buffer
          fb = (COLOR_T*)_fb2;
        } else {
          // render on the first frame buffer
          fb = (COLOR_T*)_fb1;
        }
        _win->fb = fb;
        Image::ImageData_t* data = (Image::ImageData_t*)evt.value.p;
        if ((data->width < _disp->width()) || (data->height < _disp->height())) {
          // Clear background as the image is not full screen
          memset(fb, 0, _disp->fbSize());
        }
        swim_put_image_xy(_win, (COLOR_T*)data->pixels, data->width, data->height, (_disp->width()-data->width)/2, (_disp->height()-data->height)/2);
        free(data->pointerToFree);
        _mailbox.free(data);
        if (first) {
          first = false;
          t.start();
        } else {
          while (t.read_ms() < 2000) {
            Thread::wait(100);
          }
        }
        _disp->setFramebuffer(fb);
        _active = (_active == 1 ? 2 : 1);
        t.reset();
      } else if (tLoader->get_state() == Thread::Inactive) {
        // No more images in the queue and the loader thread 
        // has completed its search
        break;
      }
    }
    
    delete tLoader;
    
    // The button must be drawn on the current framebuffer
    _btn->draw(_win->fb);

    // Wait for touches, but the AppLauncher is already listening
    // for new touch event and sends a signal to its thread which
    // is the same as runs this function so it is enough to wait
    // for that signal.
    TouchPanel* touch = DMBoard::instance().touchPanel();
    touch_coordinate_t coord;
    while(!done) {
      Thread::signal_wait(0x1);
      if (touch->read(coord) == TouchPanel::TouchError_Ok) {
        if (_btn->handle(coord.x, coord.y, coord.z > 0)) {
            _btn->draw();
        }
      }
    }
    
    // User has clicked the button, restore the original FB
    _disp->swapFramebuffer(oldFB);
    swim_window_close(_win);
}

bool AppImageViewer::teardown()
{
    if (_win != NULL) {
        free(_win);
        _win = NULL;
    }
    if (_fb1 != NULL) {
        free(_fb1);
        _fb1 = NULL;
    }
    if (_fb2 != NULL) {
        free(_fb2);
        _fb2 = NULL;
    }
    if (_btn != NULL) {
        delete _btn;
        _btn = NULL;
    }
    return true;
}

void AppImageViewer::addResource(Resources id, Resource* res)
{
    _resOk = res;
}
