/*
 * Copyright (C) 1992 by Software Research Associates, Inc.
 *      Author: Y. Kawabe <kawabe@sra.co.jp>
 *
 * Permission to use, copy, modify, and distribute, and sell this software
 * and its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Software Research Associates not be
 * used in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  Software Research Associates
 * makes no representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 */


/*
 * class isoCodeCvt (ISO2022 <-> WChar)
 */

#define isoCodeCvt _lib_nls(isoCodeCvt)

const char ESC = 033;

#define ISO2022_OPT_DE	0x01	/* use designation sequence */
#define ISO2022_OPT_OL	0x02	/* use ESC 2/4 2/[0-2] sequence */
#define ISO2022_OPT_SS	0x04	/* use single-shift sequece */
#define ISO2022_OPT_LS	0x08	/* use locking-shift sequece */

class isoCodeCvt : public CodeCvt {
  public:
    enum LorR { Left, Right };

  public:
    isoCodeCvt ();

  public:
    virtual CodeCvt* duplicate();
    virtual void setparam(const char*, const char*);

  public:
    virtual int put (ostream&, const WChar&);
    virtual int get (istream&, WChar&);
    
  protected:
    const isoCodeCvt *parent_;

  protected:
    CharSet_T GC_;		/* CharSet for C0 & C1 */
    CharSet_T G0_;		/* CharSet for G0 */
    CharSet_T G1_;		/* CharSet for G1 */
    CharSet_T G2_;		/* CharSet for G2 */
    CharSet_T G3_;		/* CharSet for G3 */

    const CharSetRep *RC_;	/* CharSetRep for C0 & C1 */
    const CharSetRep *R0_;	/* CharSetRep for G0_ */
    const CharSetRep *R1_;	/* CharSetRep for G1_ */
    const CharSetRep *R2_;	/* CharSetRep for G2_ */
    const CharSetRep *R3_;	/* CharSetRep for G3_ */

    CharSet_T* GL_;		/* CharSet for GL */
    CharSet_T* GR_;		/* CharSet for GR */

    const CharSetRep **RL_;	/* CharSetRep for GL */
    const CharSetRep **RR_;	/* CharSetRep for GR */

    long  opts_;		/* encoding method */
};

//

isoCodeCvt::isoCodeCvt () {
    parent_ = nil;

    GC_ = CharSet::ascii();
    G0_ = CharSet::find(CharSet::Single, 94, 0x42);
    G1_ = CharSet::find(CharSet::Single, 96, 0x41);
    G2_ = -1;
    G3_ = -1;

    RC_ = CharSet::rep(GC_);
    R0_ = CharSet::rep(G0_);
    R1_ = CharSet::rep(G1_);
    R2_ = CharSet::rep(G2_);
    R3_ = CharSet::rep(G3_);

    GL_ = &G0_, GR_ = &G1_, RL_ = &R0_, RR_ = &R1_;
    
    opts_ = ISO2022_OPT_DE;
}

CodeCvt* isoCodeCvt::duplicate () {
    isoCodeCvt *copy = new isoCodeCvt ();
    copy->parent_ = this;

    copy->GC_ = GC_;		copy->RC_ = RC_;
    copy->G0_ = G0_;		copy->R0_ = R0_;
    copy->G1_ = G1_;		copy->R1_ = R1_;
    copy->G2_ = G2_;		copy->R2_ = R2_;
    copy->G3_ = G3_;		copy->R3_ = R3_;

    copy->opts_ = opts_;
    return copy;
}

void isoCodeCvt::setparam (const char* key, const char* value) {
    if (strcmp (key, "G0") == 0) {
	G0_ = CharSet::find(value);
	R0_ = CharSet::rep(G0_);
    } else if (strcmp (key, "G1") == 0) {
	G1_ = CharSet::find(value);
	R1_ = CharSet::rep(G1_);
    } else if (strcmp (key, "G2") == 0) {
	G2_ = CharSet::find(value);
	R2_ = CharSet::rep(G2_);
    } else if (strcmp (key, "G3") == 0) {
	G3_ = CharSet::find(value);
	R3_ = CharSet::rep(G3_);
    } else if (strcmp (key, "OPT") == 0) {
	if (strcmp (value, "OLD") == 0) {
	    opts_ |= ISO2022_OPT_OL;
	} else if (strcmp (value, "EUC") == 0) {
	    opts_ |= ISO2022_OPT_SS;
	    opts_ &= ~ISO2022_OPT_DE;
	}
    }
}

//
// read WChar from input stream
//

int isoCodeCvt::get (istream& is, WChar& wc) {

    register int	count = 0;
    register int	ch;

#define PUT(CH, GS, REP)						      \
    if (REP) {								      \
	register int c = (CH) & 0x7f;					      \
	for (register int i = 1; is.good() && i < (REP)->bytes(); i++) {      \
	    c = (c << 8) | (is.get() & 0x7f); count ++;			      \
	}								      \
	wc.set(c, GS);							      \
    } else {								      \
	wc.set(' ', CharSet::ascii());					      \
    }
    
    /*
     * 1. Do not support to designate another set as the C0 or C1 set.
     *    Designate ISO646 as the C0 set, and ISO6429 as the C1 set.
     *
     * 2. Do not support announcement of extension facilities.
     *    Initial announcement is
     * 		01/11 02/00 04/03
     *		01/11 02/00 04/07
     *		01/11 02/00 04/09
     *		01/11 02/00 04/11
     *		01/11 02/08 04/02
     *		01/11 02/13 04/01
     *
     * 3. Do not support Dynamically Redifinable Character Sets.
     *
     */

    while (is.good()) {

	ch = is.get(), count++;

	if (ch > 0x20 && ch < 0x7f) {		/* GL */
	    PUT(ch, *GL_, *RL_);
    	    return count;
	} else if (ch >= 0xa0 && ch <= 0xff) {	/* GR */
	    PUT(ch, *GR_, *RR_);
    	    return count;
	} else if (ch == 0x20 || ch == 0x7f) {	/* SP & DEL */
	    if (*RL_ && (*RL_)->chars() == 96) {
		PUT (ch, *GL_, *RL_);
	    } else {
		PUT (ch, GC_, RC_);
	    }
    	    return count;
	} else if (ch == 0xa0 || ch == 0xff) {	/* SP8 & DEL8 */
	    if (*RR_ && (*RR_)->chars() == 96) {
		PUT (ch, *GR_, *RR_);
	    } else {
		PUT (ch, GC_, RC_);
	    }
    	    return count;
	} else if (ch >= 0x07 && ch <= 0x13) {
	    PUT (ch, GC_, RC_);	 	/* Bell & Format Effectors */
    	    return count;
	} else if (ch == ESC) {
	    ch = is.get(), count++;
	    if (ch < 0x20) {
		PUT (ch, GC_, RC_);	 	/* unknown control sequence */
    	        return count;
	    } else if (ch < 0x30) {
		if (ch == 0x20) {
		    is.ignore();		// extention announcement
		} else if (ch == 0x21) {
		    is.ignore();		// designate C0
		} else if (ch == 0x22) {
		    is.ignore();		// designate C1
		} else if (ch == 0x23) {
		    is.ignore();	// single additional control functions
		} else if (ch == 0x24) {
		    /* multi-byte representation */
	    	    ch = is.get(), count++;
		    if (ch == 0x28) {
			/* designate 94^n characters as the G0 set */
	    	        ch = is.get(), count++;
			G0_ = CharSet::find(CharSet::Multiple, 94, ch);
			R0_ = CharSet::rep(G0_);
		    } else if (ch == 0x29) {
			/* designate 94^n characters as the G1 set */
	    	        ch = is.get(), count++;
			G1_ = CharSet::find(CharSet::Multiple, 94, ch);
			R1_ = CharSet::rep(G1_);
		    } else if (ch == 0x2a) {
			/* designate 94^n characters as the G2 set */
	    	        ch = is.get(), count++;
			G2_ = CharSet::find(CharSet::Multiple, 94, ch);
			R2_ = CharSet::rep(G2_);
		    } else if (ch == 0x2b) {
			/* designate 94^n characters as the G3 set */
	    	        ch = is.get(), count++;
			G3_ = CharSet::find(CharSet::Multiple, 94, ch);
			R3_ = CharSet::rep(G3_);
		    } else if (ch == 0x2c) {
			/* designate 96^n characters as the G0 set */
	    	        ch = is.get(), count++;
			G0_ = CharSet::find(CharSet::Multiple, 96, ch);
			R0_ = CharSet::rep(G0_);
		    } else if (ch == 0x2d) {
			/* designate 96^n characters as the G1 set */
	    	        ch = is.get(), count++;
			G1_ = CharSet::find(CharSet::Multiple, 96, ch);
			R1_ = CharSet::rep(G1_);
		    } else if (ch == 0x2e) {
			/* designate 96^n characters as the G2 set */
	    	        ch = is.get(), count++;
			G2_ = CharSet::find(CharSet::Multiple, 96, ch);
			R2_ = CharSet::rep(G2_);
		    } else if (ch == 0x2f) {
			/* designate 96^n characters as the G3 set */
	    	        ch = is.get(), count++;
			G3_ = CharSet::find(CharSet::Multiple, 96, ch);
			R3_ = CharSet::rep(G3_);
		    } else if (ch == 0x40 || ch == 0x41 || ch == 0x42) {
			/* designate 94^n characters as the G0 set */
			G0_ = CharSet::find(CharSet::Multiple, 94, ch);
			R0_ = CharSet::rep(G0_);
		    }
		} else if (ch == 0x25) {
		    is.ignore();		/* other coding systems */
		} else if (ch == 0x26) {
		    is.ignore();	/* revision of registered sets */
		} else if (ch == 0x28) {
		    /* designate 94 characters as the G0 set */
	    	    ch = is.get(), count++;
		    G0_ = CharSet::find(CharSet::Single, 94, ch);
		    R0_ = CharSet::rep(G0_);
		} else if (ch == 0x29) {
		    /* designate 94 characters as the G1 set */
	    	    ch = is.get(), count++;
		    G1_ = CharSet::find(CharSet::Single, 94, ch);
		    R1_ = CharSet::rep(G1_);
		} else if (ch == 0x2a) {
		    /* designate 94 characters as the G2 set */
	    	    ch = is.get(), count++;
		    G2_ = CharSet::find(CharSet::Single, 94, ch);
		    R2_ = CharSet::rep(G2_);
		} else if (ch == 0x2b) {
		    /* designate 94 characters as the G3 set */
	    	    ch = is.get(), count++;
		    G3_ = CharSet::find(CharSet::Single, 94, ch);
		    R3_ = CharSet::rep(G3_);
		} else if (ch == 0x2c) {
		    /* designate 96 characters as the G0 set */
	    	    ch = is.get(), count++;
		    G0_ = CharSet::find(CharSet::Single, 96, ch);
		    R0_ = CharSet::rep(G0_);
		} else if (ch == 0x2d) {
		    /* designate 96 characters as the G1 set */
	    	    ch = is.get(), count++;
		    G1_ = CharSet::find(CharSet::Single, 96, ch);
		    R1_ = CharSet::rep(G1_);
		} else if (ch == 0x2e) {
		    /* designate 96 characters as the G2 set */
	    	    ch = is.get(), count++;
		    G2_ = CharSet::find(CharSet::Single, 96, ch);
		    R2_ = CharSet::rep(G2_);
		} else if (ch == 0x2f) {
		    /* designate 96 characters as the G3 set */
	    	    ch = is.get(), count++;
		    G3_ = CharSet::find(CharSet::Single, 96, ch);
		    R3_ = CharSet::rep(G3_);
		} else {
		    PUT (ch, GC_, RC_);
    	            return count;
		}
	    } else if (ch < 0x40) {		/* private use */
		PUT (ch, GC_, RC_);
    	        return count;
	    } else if (ch < 0x60) {
		if (ch == 0x4e) {		/* Single-Shift-Two */
	    	    ch = is.get(), count++;
		    PUT (ch, G2_, R2_);
    	            return count;
		} else if (ch == 0x4e) {	/* Single-Shift-Three */
	    	    ch = is.get(), count++;
		    PUT (ch, G3_, R3_);
    	            return count;
		} else {
		    PUT (ch, GC_, RC_);
    	            return count;
		}
	    } else if (ch < 0x7f) {
		if (ch == 0x6e) {		/* Locking-Shift-Two */
	    	    GL_ = &G2_; RL_ = &R2_;
		} else if (ch == 0x6f) {	/* Locking-Shift-Three */
	    	    GL_ = &G3_; RL_ = &R3_;
		} else if (ch == 0x7e) {	/* Locking-Shift-One-R */
		    GR_ = &G1_; RR_ = &R1_;
		} else if (ch == 0x7d) {	/* Locking-Shift-Two-R */
		    GR_ = &G2_; RR_ = &R2_;
		} else if (ch == 0x7c) {	/* Locking-Shift-Three-R */
		    GR_ = &G3_; RR_ = &R3_;
		} else {
		    PUT (ch, GC_, RC_);
    	            return count;
		}
	    } else {
		PUT (ch, GC_, RC_);
    	        return count;
	    }
	} else if (ch == 0x0e) {	/* Locking-Shift-One */
	    GL_ = &G1_; RL_ = &R1_;
	} else if (ch == 0x0f) {	/* Locking-Shift-Zero */
	    GL_ = &G0_; RL_ = &R0_;
	} else if (ch == 0x8e) {	/* Single-Shift-Two-R */
	    ch = is.get(), count++;
	    PUT (ch, G2_, R2_);
    	    return count;
	} else if (ch == 0x8f) {	/* Single-Shift-Three-R */
	    ch = is.get(), count++;
	    PUT (ch, G3_, R3_);
    	    return count;
	} else if (ch >= 0x84 && ch <= 0x8d) {
	    /* format effector & selected area */
	    PUT (ch, GC_, RC_);
    	    return count;
	}
    }
    return -1;				/* return EOF */
}

#undef PUT

//
// write WChar to output stream
//

int isoCodeCvt::put (ostream& os, const WChar& c) {
    
    register int ch = c.charcode();
    register CharSet_T G = c.charset();
    register int count = 0;
	
#define PUT(CH, REP, DIR)						      \
    if (REP) {								      \
	for (register int j = (REP)->bytes() - 1; j > 0; j --) {	      \
	    ch = CH.charcode() >> (8 * j);				      \
	    os.put((DIR == Left) ? ch & 0x7f : (ch & 0xff) | 0x80); count++;  \
        }								      \
	ch = CH.charcode();						      \
	os.put((DIR == Left) ? ch & 0x7f : (ch & 0xff) | 0x80); count++;      \
    }

    if (c.value() == EOF) {

        if (!parent_) { return 0; }
    
        CharSet_T G0 = parent_->G0_;
        CharSet_T G1 = parent_->G1_;
        const CharSetRep *R0 = parent_->R0_;
        const CharSetRep *R1 = parent_->R1_;

        if (G0_ != G0 && R0) {			/* restore G0 */
	    os.put(ESC); count++;
	    if ((opts_ & ISO2022_OPT_OL) &&
		R0->bytes() > 1 && R0->chars() == 94 &&
	        R0->final() >= 0x40 && R0->final() <= 0x42) {
	        os.put('$'); count++;
	        os.put(R0->final()); count++;
	    } else {
	        if (R0->bytes() > 1) {
		    os.put('$'); count++;
	        }
	        os.put(0x28); count++;
	        os.put(R0->final()); count++;
	    }
	    G0_ = G0; R0_ = R0;
        }
        
        if (G1_ != G1 && R1) {			/* restore G1 */
	    os.put(ESC); count++;
	    if (R1->bytes() > 1) {
	        os.put('$'); count++;
	    }
	    os.put(0x28); count++;
	    os.put(R1->final()); count++;
	    G1_ = G1; R1_ = R1;
        }
    } else if (G == G0_ && R0_) {
	PUT(c, R0_, Left);
    } else if (G == G1_ && R1_) {
	PUT(c, R1_, Right);
    } else if ((opts_ & ISO2022_OPT_SS) && G == G2_ && R2_) {
	os.put(0x8e); count++;	/* Single-Shift-Two-R */
	PUT(c, R2_, Right);
    } else if ((opts_ & ISO2022_OPT_SS) && G == G3_ && R3_) {
	os.put(0x8f); count++;	/* Single-Shift-Three-R */
	PUT(c, R3_, Right);
    } else if (opts_ & ISO2022_OPT_DE) {
	const CharSetRep* R = CharSet::rep(G);
	if (R) {
	    os.put(ESC); count++;
	    if ((opts_ & ISO2022_OPT_OL) &&
		R->bytes() > 1 && R->chars() == 94 &&
		R->final() >= 0x40 && R->final() <= 0x42) {
		os.put('$'); count++;
		os.put(R->final()); count++;
		G0_ = G; R0_ = R;
		PUT (c, R, Left);
	    } else {
		if (R->bytes() > 1) { os.put('$'); count++;}
		if (R->chars() == 96) {
		    os.put(0x2d); count++;
		    os.put(R->final()); count++;
		    G1_ = G; R1_ = R;
		    PUT (c, R, Right);
		} else {
		    os.put(0x28); count++;
		    os.put(R->final()); count++;
		    G0_ = G; R0_ = R;
		    PUT (c, R, Left);
		}
	    }
	}
    } else if (G == GC_ && (ch <= 0x20 || ch >= 0x7f)) {
	os.put(ch); count++;
    }
    return count;
}

#undef PUT

