/*
 * -+* Prescribble *+-
 * 
 * by Kent Dahl ( MenThal@bigfoot.com )
 *
 * Jot down illegible notes for your self, quickly and easily without
 * arguing with a HWR engine or cryptic input.
 *
 * "Prescribble: Just as illegible as the prescription from your doctor!"
 * "Prescribble: So hard to read, you'll need prescription glasses!"
 *
 * A FLTK based program for the Agenda VR3.
 * 
 */

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Browser.H>
#include <FL/Fl_Pack.H>
#include <FL/Fl_Menu_Button.H>
#include <FL/fl_draw.H>


#include <iostream>
#include <fstream>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <cstdio>
#include <vector>
#include <string>
#include <algorithm>

#include "prescribble.hpp"

#include "presaction.hpp"

#ifndef DEBUG
#define DEBUG 1
#endif

//===============

class PresStrokeAction : public PresActionBase {
protected:
    PrescribbleStroke *m_pStroke;
public:
    PresStrokeAction( PrescribbleNote   *a_note,
                      PrescribbleStroke *a_stroke)
        : PresActionBase(a_note),
          m_pStroke(a_stroke)
        {
            if( !m_pStroke){
                m_pStroke = a_note->m_strokes.back();
            }
            std::printf("new Pres...StrokeAction\n");
        }

    bool redo( Context *a_pContex = 0 )
        {
            if( m_pStroke ) {
                m_pNote->add( *m_pStroke );
                m_done = true;
                if(DEBUG)std::printf(" Stroke - redo \n");
            }else{
            }
            return m_done ;
        }


    bool undo( Context *a_pContex = 0 )
        {
            if( m_pStroke ) {
                m_pNote->remove( *m_pStroke );
                m_done = false;
                if(DEBUG)std::printf(" Stroke - undo \n");
            }else{
            }
            return !m_done ;
        }

};

class PresDeleteStrokeAction : public PresStrokeAction {
public:
    PresDeleteStrokeAction( PrescribbleNote   *a_note,
                            PrescribbleStroke *a_stroke)
        : PresStrokeAction(a_note,a_stroke)
        {
            if(DEBUG)std::printf("new Pres...StrokeAction\n");
        }


    bool undo( Context *a_pContex = 0 )
        {
            if( m_pStroke ) {
                m_pNote->add( *m_pStroke );
                m_done = false;
                if(DEBUG)std::printf(" DelStroke - undo \n");
            }else{
            }
            return !m_done ;
        }


    bool redo( Context *a_pContex = 0 )
        {
            if( m_pStroke ) {
                m_pNote->remove( *m_pStroke );
                m_done = true;
                if(DEBUG)std::printf(" DelStroke - redo \n");
            }else{
            }
            return m_done ;
        }




/*
    bool undo( Context *a_pContex = 0 ){
        if(DEBUG)std::printf(" DeleteStroke - undo \n");
        return PresStrokeAction::redo();
    }
    bool redo( Context *a_pContex = 0 ){
        if(DEBUG)std::printf(" DeleteStroke - redo \n");
        return PresStrokeAction::undo();
    }
*/
    bool isDone(){ return m_done; }
};


////////////////// Point2D /////////////////////

Point2D::Point2D(int a_x , int a_y):x(a_x),y(a_y){}

bool Point2D::isWithinRange( Point2D dst, int range )
{
    // quick and dirty way to do it: binary digit grid based
    int a = x - dst.x;
    int b = y - dst.y;

    return( a <= range && a >= -range && b <= range && b >=-range );
}


unsigned int Point2D::manhattan( Point2D dst )
{
    int a = x - dst.x;
    int b = y - dst.y;
    a = ( a<0 ? -a : a );
    b = ( b<0 ? -b : b );

    //if(DEBUG &&(a+b)<25)std::printf(" (%i,%i).manhattan((%i,%i))==%i\n",
    //                     x,y,dst.x,dst.y,a+b);
    return a+b;

}

void Point2D::writeto( ostream & out ){
    out << x <<","<<y;
}

Point2D  Point2D::operator+(const Point2D& p)
{
    return Point2D( x+p.x, y+p.y );
}
////////////////////

/** 
 * creates a rectangle in "zombie" mode.
 * Until it isValid, only isValid and fenceIn are legal
 * operations on it
 */
Rect2D::Rect2D()
    : ul(256,256), br(-256,-256)
{}

Rect2D::Rect2D( Point2D a, Point2D b )
{
    ul = Point2D( (a.x < b.x ? a.x : b.x ),
                  (a.y < b.y ? a.y : b.y ));
    br = Point2D( (a.x > b.x ? a.x : b.x ),
                  (a.y > b.y ? a.y : b.y ));
}

void Rect2D::fenceIn( Point2D p)
{
    if( isValid() ){
        if( !isWithin(p) )
        {
            ul.x = (ul.x <= p.x) ? ul.x : p.x;
            ul.y = (ul.y <= p.y) ? ul.y : p.y;
            br.x = (br.x >= p.x) ? br.x : p.x;
            br.y = (br.y >= p.y) ? br.y : p.y;
        }
    }
    else
        ul = br = p;
}


bool Rect2D::isValid() const
{
    return ul.x<=br.x && ul.y<=br.y;
}

bool Rect2D::isWithin( Point2D p ) const
{
    return (p.y >= ul.y && 
            p.y <= br.y &&
            p.x >= ul.x && 
            p.x <= br.x );
}

Rect2D::CollisionType Rect2D::collides( Rect2D r ) const
{ 
    bool ulcoll = r.isWithin( ul );
    bool brcoll = r.isWithin( br );

    if( ulcoll && brcoll )
        return INSIDE;
    else if( ulcoll || brcoll )
        return OVERLAP;
    else 
    {
        ulcoll = isWithin( r.ul );
        brcoll = isWithin( r.br );

        if( ulcoll && brcoll )
            return OUTSIDE;
        else if( ulcoll || brcoll )
            return OVERLAP;

    }
       
    return NONE; 
}

int Rect2D::width() const
{
    return br.x - ul.x +1;
}
int Rect2D::height() const
{
    return br.y - ul.y +1;
}

////////////////////

const char *Pres::NOTEBOOK_TAG_BEGIN = "<notebook>";
const char *Pres::NOTE_TAG_BEGIN = "<note>";
const char *Pres::STROKE_TAG_BEGIN = "<stroke>";

const char *Pres::NOTEBOOK_TAG_END = "</notebook>";
const char *Pres::NOTE_TAG_END = "</note>";
const char *Pres::STROKE_TAG_END = "</stroke>";





//================ PerscribbleStroke ======================
void PrescribbleStroke::add( Point2D p)
{
    m_points.push_back(p);
    m_boundingRect.fenceIn(p);
}


/**
 * clips the stroke into several strokes, with nothing inside the
 * given rectangular area.
 */
PrescribbleStroke::StrokePtrVector PrescribbleStroke::clippingAway( 
    Rect2D a_rect, bool keepInside )
{
    PrescribbleStroke *currStroke = 0;
//    bool previnside = false;

    StrokePtrVector result;

    Point2DVector::iterator it    = m_points.begin();
    Point2DVector::iterator itend = m_points.end();
    
    if(it == itend) return result;
    
    while( it != itend )
    {
        bool inside = a_rect.isWithin( (*it) );

        if( inside==keepInside ) //keep it!
        {
            if(!currStroke){
                currStroke = new PrescribbleStroke();
                result.push_back( currStroke );
            }
            currStroke->add( *it );
            
        }
        else // loose it
        {
            currStroke = 0;
        }
        it++;
    }

    return result;
}

/**
 * iterate out ALL the lines
 */
void PrescribbleStroke::drawAllLines( DrawingContext &a_context )
{
    Point2DVector::iterator it    = m_points.begin();
    Point2DVector::iterator itend = m_points.end();
    
    if(it == itend) return;
    
    Point2D src = (*it);
    it++;
    while( it != itend )
    {
        a_context.drawLine( src, (*it));
        src = (*it);
        it++;
    }

}


void PrescribbleStroke::writeto( ostream & out ){
    Point2DVector::iterator it    = m_points.begin();
    Point2DVector::iterator itend = m_points.end();
    
    out  <<Pres::STROKE_TAG_BEGIN<<"\n";
    while( it != itend )
    {
        it->writeto( out );
        out << " ";
        it++;
    }
    out  <<Pres::STROKE_TAG_END<<  "\n";
}



//============== PrescribbleNote ================0




PrescribbleNote::PrescribbleNote( PresNoteConfig &a_config,
                                  PrescribbleNoteListener *a_pListener = 0) 
    : m_config(a_config), m_pListener( a_pListener )
{}
PrescribbleNote::~PrescribbleNote()
        {
            // delete the strokes too
            while( !m_strokes.empty()){
                delete m_strokes.back();
                m_strokes.pop_back();
            }

            while( !m_undoableActions.empty()){
                delete m_undoableActions.back();
                m_undoableActions.pop_back();
            }

            while( !m_redoableActions.empty()){
                delete m_redoableActions.back();
                m_redoableActions.pop_back();
            }

            updateListeners();


        }

    void PrescribbleNote::beginStroke( Point2D p )
        {
            m_pCurrentStroke = new PrescribbleStroke();

            Action *action = new PresStrokeAction( 
                this, m_pCurrentStroke );

            doAction( *action );

            // m_strokes.push_back( m_pCurrentStroke );
            continueStroke( p );
        }
    void PrescribbleNote::continueStroke( Point2D p)
        {
           if( m_pCurrentStroke ) m_pCurrentStroke->add(p);
        }
    void PrescribbleNote::endStroke( Point2D p )
        {
            continueStroke( p );
            m_pCurrentStroke = 0;
        };


    void PrescribbleNote::undoStroke()
        {
            if(m_strokes.empty()) return;
            
            m_deletedStrokes.push_back( m_strokes.back() );
            m_strokes.pop_back();

            // don't store too many redos
            if( m_deletedStrokes.size() > m_config.m_maxRedoStrokes )
            {
                delete m_deletedStrokes.front();
                m_deletedStrokes.erase( m_deletedStrokes.begin() );
            }
        }

    void PrescribbleNote::redoStroke()
        {
            if(m_deletedStrokes.empty()) return;

            m_strokes.push_back( m_deletedStrokes.back() );
            m_deletedStrokes.pop_back();
        }


PrescribbleStroke *PrescribbleNote::lastStroke(){
    if(m_strokes.empty()) 
        return 0;
    return m_strokes.back();
}


void PrescribbleNote::redoAction()
{
    if( m_redoableActions.empty() )
    {
        //redoStroke();
        return;
    }

    if(m_redoableActions.back()->redo())
    {
        if(DEBUG)std::printf("redo\n");
        m_undoableActions.push_back( m_redoableActions.back() );
        m_redoableActions.pop_back();
    }

    updateListeners();
}
void PrescribbleNote::undoAction()
{
    if( m_undoableActions.empty() )
    {
        //undoStroke();
        return;
    }

    if(m_undoableActions.back()->undo())
    {
        if(DEBUG)std::printf("undo\n");
        m_redoableActions.push_back( m_undoableActions.back() );
        m_undoableActions.pop_back();

        if( m_redoableActions.size()> m_config.m_maxRedoStrokes)
        {
            delete m_redoableActions.front();
            m_redoableActions.erase( m_redoableActions.begin() );
        }

    }
    updateListeners();


}

void PrescribbleNote::doAction( Action &action )
{
    if(DEBUG)std::printf(" note - doAction \n");
    action.redo();
    m_undoableActions.push_back( &action );
    updateListeners();
}

bool PrescribbleNote::anyRedosLeft(){
    return !m_redoableActions.empty();
}
bool PrescribbleNote::anyUndosLeft(){
    return !m_undoableActions.empty();
}




bool PrescribbleNote::requestEraseAt( Point2D p )
{
    
    StrokePtrVector::iterator it     = m_strokes.begin();
    StrokePtrVector::iterator itend  = m_strokes.end();
    
    //found stroke
    StrokePtrVector::iterator itfind = m_strokes.end();
    //found point
    PrescribbleStroke::Point2DVector::iterator sitfind;

    unsigned int currlen = m_config.m_eraseRange;
    unsigned int tmp;

    // find the stroke with a point closest to the erase point
    for(; it!=itend; it++)
    {
        PrescribbleStroke::Point2DVector::iterator sit 
            = (*it)->m_points.begin();
        PrescribbleStroke::Point2DVector::iterator sitend
            = (*it)->m_points.end();
        for(; sit!=sitend; sit++)
        {
            tmp = p.manhattan( *sit );
            if( tmp <= currlen )
            {
                currlen = tmp;
                itfind = it;
                sitfind = sit;
            }
        }
    }

    if(itfind!=itend) //found a stroke to erase
    {
        // erase the whole stroke
        PresAction *action = new PresDeleteStrokeAction(
            this, *itfind );
        doAction( *action );

//        m_deletedStrokes.push_back( *itfind );
//        m_strokes.erase( itfind );
        
        
        // erase just the point? flattens the stroke
        //(*itfind)->m_points.erase( sitfind );

        // we could also split the stroke at the found point?

        return true;
    }
    return false;
    

}


void PrescribbleNote::add(    PrescribbleStroke &a_stroke )
{
    m_strokes.push_back( &a_stroke );
}
void PrescribbleNote::remove( PrescribbleStroke &a_stroke )
{
    if( m_strokes.back() == &a_stroke)
    {
        m_strokes.pop_back();
    }
    else
    {
        StrokePtrVector::iterator it = std::find(
            m_strokes.begin(),
            m_strokes.end(),
            &a_stroke );
        
        if( it!=m_strokes.end() )
            m_strokes.erase( it );
    }
}



PresAction* PrescribbleNote::erase(  StrokePtrVector::iterator &it )
{
    if(DEBUG)std::printf("stroke\n" );
    // erase the whole stroke
    if(it!=m_strokes.end()){

        PresAction *action = new PresDeleteStrokeAction(
            this, *it );
        return action;
        //doAction( *action );

    }
    return 0;
}

void PrescribbleNote::erasePoint( PrescribbleNote::FindResult &found )
{
    if(found.found){
        (*found.foundStroke)->m_points.erase( found.foundPoint );
        // what about undo?
    }
}

// void PrescribbleNote::splitAtPoint( PrescribbleNote::FindResult &found )
// {
//     if(DEBUG)std::printf("splitAtPoint\n");
//     if(found.found 
//        && found.foundStroke != m_strokes.end()
//        //&& found.foundPoint != (*found.foundStroke)->m_points.end() 
//         ){
//         m_deletedStrokes.push_back( *found.foundStroke );
//         PrescribbleStroke *foundStroke = (*found.foundStroke);
//         m_strokes.erase( found.foundStroke );
//         PrescribbleStroke::Point2DVector::iterator sit 
//             = foundStroke->m_points.begin();
//         PrescribbleStroke::Point2DVector::iterator sitbegin 
//             = foundStroke->m_points.begin();
//         PrescribbleStroke::Point2DVector::iterator sitend
//             = foundStroke->m_points.end();
//         bool inside = false;
//         bool insideprev = false;
//         bool begunstroke = false;
//         while(sit!=sitend){
//             insideprev = inside;
//             if(DEBUG)std::printf("loop...\n");
//             Point2D p = *sit;
//             inside = 
//                 found.searchPoint.isWithinRange( (*sit),
//                                                  m_config.m_eraseRange )
//                 ;
//             if(DEBUG)std::printf(" inside: %i  %i\n",inside,insideprev);
//             if(inside)
//             {
//                 if(DEBUG)std::printf("  inside... \n");
//                 if(!insideprev && begunstroke )
//                 {
//                     begunstroke = false;
//                     endStroke( p );
//                 }//dropmode = true;
//                 if(DEBUG)std::printf("  inside \n");
//             }
//             else
//             {
//                 if(DEBUG)std::printf("  not inside... \n");
//                 if(!begunstroke){
//                     if(DEBUG)std::printf("  beginstroke \n");
//                     beginStroke( p );
//                     begunstroke = true;
//                 }
//                 else
//                     continueStroke( p );
//                 if(DEBUG)std::printf("  not inside \n");
//             }
//             if(DEBUG)std::printf("loop!\n");
//             sit++;
//             if(DEBUG)std::printf("next loop...\n");
//         }
//     }
// }

PrescribbleNote::Action* PrescribbleNote::splitAtPoint( 
    PrescribbleNote::FindResult &found )
{
    if(found.found 
       && found.foundStroke != m_strokes.end()
       && found.foundPoint != (*found.foundStroke)->m_points.end() 
        ){
        m_deletedStrokes.push_back( *found.foundStroke );
 
        PrescribbleStroke *s1, *s2;
        s1 = new PrescribbleStroke();
        s2 = new PrescribbleStroke();


        PrescribbleStroke *foundStroke = (*found.foundStroke);

        PresComplexAction *action = new PresComplexAction();
        action->add( *new PresDeleteStrokeAction( 
            this, *found.foundStroke ) );
        
//        m_strokes.erase( found.foundStroke );

        PrescribbleStroke::Point2DVector::iterator sit 
            = foundStroke->m_points.begin();
        PrescribbleStroke::Point2DVector::iterator sitbegin 
            = foundStroke->m_points.begin();
        PrescribbleStroke::Point2DVector::iterator sitend
            = foundStroke->m_points.end();

        while(sit!=found.foundPoint && sit!=sitend){
            s1->add( *sit );
            sit++;
        }
        
        sit = sitend;
        do{
            sit--;
            s2->add( *sit );
        }
        while(sit!=found.foundPoint && sit!=sitbegin);
        
        if(s1->m_points.size()>1) 
            action->add( *new PresStrokeAction( this, s1 ) );
        //            m_strokes.push_back( s1 );
        else 
        {
            delete s1;
            if(DEBUG)std::printf(" delete s1\n");
        }
        if(s2->m_points.size()>1){
            if(DEBUG)std::printf(" added s2\n");
             action->add( *new PresStrokeAction( this, s2 ) ); 
        //            m_strokes.push_back( s2 );
        }
        else 
        {
            delete s2;
            if(DEBUG)std::printf(" delete s2\n");
        }


        return action;
    }
    return 0;
}/**/


PresAction *PrescribbleNote::eraseRectAction( Rect2D a_rect,
                                              DrawingContext* a_pContext )
{
    StrokePtrVector::iterator it     = m_strokes.begin();
    StrokePtrVector::iterator itend  = m_strokes.end();

    PresComplexAction* action = new PresComplexAction();

    for(; it!=itend; it++)
        if( a_rect.collides( (*it)->getBoundingRect() ) )
        {
            StrokePtrVector tmp =
                (*it)->clippingAway( a_rect );
            if(DEBUG)std::printf(" stroke remains: %i\n",tmp.size() );

            if(a_pContext){ // draw over it
                fl_color( FL_WHITE );
                (*it)->drawAllLines( *a_pContext );
                fl_color( FL_BLACK );
            }

            while(!tmp.empty())
            {
                action->add( *new PresStrokeAction( this, tmp.back() ) );
                if( a_pContext )
                    tmp.back()->drawAllLines( *a_pContext );
                tmp.pop_back();
            }
            action->add( *(new PresDeleteStrokeAction( this, *it )) );
            //delact.redo();
        }
    return action;
}
        
void PrescribbleNote::eraseRect( Rect2D a_rect )
{
    PresAction* action = eraseRectAction( a_rect );
    doAction( *action );

}


PrescribbleNote::FindResult PrescribbleNote::findPoint( Point2D p )
{
    
    StrokePtrVector::iterator it     = m_strokes.begin();
    StrokePtrVector::iterator itend  = m_strokes.end();
    
    //found stroke
//    StrokePtrVector::iterator itfind = m_strokes.end();
    //found point
//    PrescribbleStroke::Point2DVector::iterator sitfind = (*it)->m_points.end();

    FindResult result;
    result.found = false;
    result.searchPoint = p;

    unsigned int currlen = m_config.m_eraseRange;
    unsigned int tmp;

    // find the stroke with a point closest to the erase point
    for(; it!=itend; it++)
    {
        PrescribbleStroke::Point2DVector::iterator sit 
            = (*it)->m_points.begin();
        PrescribbleStroke::Point2DVector::iterator sitend
            = (*it)->m_points.end();
        while(sit!=sitend)
        {
            tmp = p.manhattan( *sit );
            if( tmp <= currlen )
            {
                result.found = true;
                result.foundStroke =  it;
                result.foundPoint  = sit;
                result.manhattan   = tmp;

                currlen = tmp;
//                itfind = it;
//                sitfind = sit;
            }
            sit++;
        }
    }
    return result;
    

}





    void PrescribbleNote::writeto( ostream & out ){
        StrokePtrVector::iterator it    = m_strokes.begin();
        StrokePtrVector::iterator itend = m_strokes.end();

        out << "<note>\n";
        while( it != itend )
        {
            (*it)->writeto( out );
            //out << " ";
            it++;
        }
        out << "</note>\n";
    }


    void PrescribbleNote::cleanOut(){
        // delete the strokes too
        StrokePtrVector::iterator it    = m_strokes.begin();
        StrokePtrVector::iterator itend = m_strokes.end();

        PresComplexAction *action = new PresComplexAction();
        while( it != itend ){
            action->add( *new PresDeleteStrokeAction( this, *it ) );
            it++;
        }

        doAction( *action );

//         while( !m_strokes.empty()){
//             m_deletedStrokes.push_back( m_strokes.back() );
//             m_strokes.pop_back();
//         }
    }

    /**
     * iterate out ALL the lines
     */
void PrescribbleNote
::drawAllLines( PrescribbleStroke::DrawingContext &a_context )
{
    StrokePtrVector::iterator it    = m_strokes.begin();
    StrokePtrVector::iterator itend = m_strokes.end();
    while( it != itend )
    {
        (*it)->drawAllLines( a_context );
        it++;
    }

}

void PrescribbleNote
::drawLastStroke( PrescribbleStroke::DrawingContext &a_context )
{
    if( !m_strokes.empty() )
        m_strokes.back()->drawAllLines( a_context );

}





void PrescribbleNote::updateListeners()
{
    if( m_pListener )
    {
        m_pListener->updateUndoStatus( anyUndosLeft() );
        m_pListener->updateRedoStatus( anyRedosLeft() );
    }
}
