import * as code from './code';
import * as o from './operand';
import {Definition, IDefinition} from './def';
import * as i from './instruction';
import * as p from './parts';
import {extend} from './util';


export namespace x64 {

    const defDefaults: IDefinition = {
        size: 32,
        addrSize: 64,
    };

    function insdef(defs: IDefinition) {
        return new Definition(extend<IDefinition>({}, defDefaults, defs));
    }

    const INC = insdef({op: 0xFF, opreg: 0b000});
    const DEC = insdef({op: 0xFF, opreg: 0b001});


    export class Instruction extends i.Instruction {

        prefixRex: p.PrefixRex = null;

        protected writePrefixes(arr: number[]) {
            super.writePrefixes(arr);
            if(this.prefixRex) this.prefixRex.write(arr);
        }

        protected needsOperandSizeChange() {
            return (this.def.size == o.SIZE.DOUBLE) && this.hasRegisterOfSize(o.SIZE.QUAD);
        }

        protected createPrefixes() {
            super.createPrefixes();
            if(this.def.mandatoryRex || this.needsOperandSizeChange() || this.hasExtendedRegister())
                this.prefixRex = this.createRex();
        }

        protected createRex(): p.PrefixRex {
            var W = 0, R = 0, X = 0, B = 0;

            if(this.needsOperandSizeChange()) W = 1;

            var {dst, src} = this.op;
            if(dst && dst.reg() && (dst.reg() as o.Register).isExtended()) R = 1;
            if(src && src.reg() && (src.reg() as o.Register).isExtended()) B = 1;

            var mem: o.Memory = this.getMemoryOperand();
            if(mem) {
                if(mem.base && mem.base.isExtended()) B = 1;
                if(mem.index && mem.index.isExtended()) X = 1;
            }

            // if(!this.regToRegDirectionRegIsDst) [R, B] = [B, R];
            return new p.PrefixRex(W, R, X, B);
        }
    }

    export class Code extends code.Code {
        protected ClassInstruction = Instruction;
        
        mode = code.MODE.LONG;

        incq(operand: o.Register);
        incq(operand: o.Memory);
        incq(operand: o.Register|o.Memory) {
            return this.insert(INC, this.createOperands(operand));
        }

        decq(operand: o.Register);
        decq(operand: o.Memory);
        decq(operand: o.Register|o.Memory) {
            return this.insert(DEC, this.createOperands(operand));
        }
    }


    // Generates logically equivalent code to `Instruction` but the actual
    // bytes of the machine code will likely differ, because `FuzzyInstruction`
    // picks at random one of the possible instructions when multiple instructions
    // can perform the same operation. Here are some examples:
    //
    //  - Bits in `REX` prefix are ignored if they don't have an effect on the instruction.
    //  - Register-to-register `MOV` instruction can be encoded in two different ways.
    //  - Up to four prefixes may be added to instruction, if they are not used, they are ignored.
    //  - There can be many different *no-op* instruction that are used to fill in padding, for example:
    //
    //     mov %rax, %rax
    //     add $0, %rax
    export class FuzzyInstruction extends Instruction {
        protected regToRegDirectionRegIsDst = !(Math.random() > 0.5);

        protected oneOrZero(): number {
            return Math.random() > 0.5 ? 1 : 0;
        }

        // Randomize unused bits in REX byte.
        protected createRex(dstreg: o.Register, dstmem: o.Memory, srcreg: o.Register, srcmem: o.Memory) {
            var rex: p.PrefixRex = super.createRex(dstreg, dstmem, srcreg, srcmem);

            if(!dstmem && !srcmem) {
                rex.X = this.oneOrZero();
                if(!srcreg) rex.B = this.oneOrZero();
            }

            return rex;
        }
    }

    export class FuzzyCode extends Code {
        protected ClassInstruction = FuzzyInstruction;

        nop(size = 1) {

        }
    }

}
