package nl.grauw.glass.expressions;

public class Schema implements SchemaType {

    private SchemaType[] types;

    public Schema(SchemaType... types) {
        this.types = types;
    }

    @Override
    public boolean check(Expression arguments) {
        for (int i = 0; i < types.length; i++) {
            if (arguments == null || !types[i].check(arguments.getElement())) {
                return false;
            }
            arguments = arguments.getNext();
        }
        return arguments == null;
    }

    public static SchemaType ANY = new IsAny();
    public static SchemaType DIRECT = new IsDirect();
    public static SchemaType INDIRECT = new IsIndirect();
    public static SchemaType INTEGER = new IsInteger();
    public static SchemaType STRING = new IsString();
    public static SchemaType IDENTIFIER = new IsIdentifier();
    public static SchemaType DIRECT_N = new And(DIRECT, INTEGER);
    public static SchemaType DIRECT_R = new And(DIRECT, new IsRegister8Bit());
    public static SchemaType DIRECT_A = new And(DIRECT, new IsRegister(Register.A));
    public static SchemaType DIRECT_IR = new And(DIRECT, new IsRegister(Register.I, Register.R));
    public static SchemaType DIRECT_RR = new And(DIRECT, new IsRegister(Register.BC, Register.DE, Register.HL, Register.SP));
    public static SchemaType DIRECT_RR_INDEX = new And(DIRECT, new IsRegister(Register.BC, Register.DE, Register.HL, Register.SP, Register.IX, Register.IY));
    public static SchemaType DIRECT_RR_AF_INDEX = new And(DIRECT, new IsRegister(Register.BC, Register.DE, Register.HL, Register.AF, Register.IX, Register.IY));
    public static SchemaType DIRECT_DE = new And(DIRECT, new IsRegister(Register.DE));
    public static SchemaType DIRECT_HL = new And(DIRECT, new IsRegister(Register.HL));
    public static SchemaType DIRECT_HL_IX_IY = new And(DIRECT, new IsRegister(Register.HL, Register.IX, Register.IY));
    public static SchemaType DIRECT_SP = new And(DIRECT, new IsRegister(Register.SP));
    public static SchemaType DIRECT_AF = new And(DIRECT, new IsRegister(Register.AF));
    public static SchemaType DIRECT_AF_ = new And(DIRECT, new IsRegister(Register.AF_));
    public static SchemaType INDIRECT_N = new And(INDIRECT, INTEGER);
    public static SchemaType INDIRECT_C = new And(INDIRECT, new IsRegister(Register.C));
    public static SchemaType INDIRECT_BC_DE = new And(INDIRECT, new IsRegister(Register.BC, Register.DE));
    public static SchemaType INDIRECT_HL_IX_IY = new And(INDIRECT, new IsRegister(Register.HL, Register.IX, Register.IY));
    public static SchemaType INDIRECT_SP = new And(INDIRECT, new IsRegister(Register.SP));
    public static SchemaType DIRECT_R_INDIRECT_HL_IX_IY = new IsDirectRIndirectHLIXIY();

    public static class IsAny implements SchemaType {
        @Override
        public boolean check(Expression argument) {
            return true;
        }
    }

    public static class And implements SchemaType {
        private SchemaType[] types;

        public And(SchemaType... types) {
            this.types = types;
        }

        @Override
        public boolean check(Expression argument) {
            for (SchemaType type : types) {
                if (!type.check(argument)) {
                    return false;
                }
            }
            return true;
        }
    }

    public static class IsDirect implements SchemaType {
        @Override
        public boolean check(Expression argument) {
            return !argument.isGroup();
        }
    }

    public static class IsIndirect implements SchemaType {
        @Override
        public boolean check(Expression argument) {
            return argument.isGroup();
        }
    }

    public static class IsAnnotation implements SchemaType {
        private final SchemaType rhsType;

        public IsAnnotation(SchemaType rhsType) {
            this.rhsType = rhsType;
        }

        @Override
        public boolean check(Expression argument) {
            return rhsType.check(argument.getAnnotee());
        }
    }

    public static class IsInteger implements SchemaType {
        @Override
        public boolean check(Expression argument) {
            return argument.isInteger();
        }
    }

    public static class IsString implements SchemaType {
        @Override
        public boolean check(Expression argument) {
            return argument.isString();
        }
    }

    public static class IsIdentifier implements SchemaType {
        @Override
        public boolean check(Expression argument) {
            return argument instanceof Identifier;
        }
    }

    public static class IsRegister implements SchemaType {
        private Register[] registers;

        public IsRegister(Register... registers) {
            this.registers = registers;
        }

        @Override
        public boolean check(Expression argument) {
            if (argument.isRegister()) {
                Register register = argument.getRegister();
                for (Register expected : registers) {
                    if (register == expected) {
                        return true;
                    }
                }
            }
            return false;
        }
    }

    public static class IsRegister8Bit implements SchemaType {
        @Override
        public boolean check(Expression argument) {
            if (argument.isRegister()) {
                Register register = argument.getRegister();
                return !register.isPair() && register != Register.I && register != Register.R;
            }
            return false;
        }
    }

    public static class IsDirectRIndirectHLIXIY implements SchemaType {
        @Override
        public boolean check(Expression argument) {
            if (argument.isRegister()) {
                Register register = argument.getRegister();
                return DIRECT.check(argument) && !register.isPair() && register != Register.I && register != Register.R ||
                    INDIRECT.check(argument) && (register == Register.HL || register.isIndex());
            }
            return false;
        }
    }

    public static class IsFlag implements SchemaType {
        @Override
        public boolean check(Expression argument) {
            return argument.isFlag();
        }
    }

    public static class IsFlagZC implements SchemaType {
        @Override
        public boolean check(Expression argument) {
            return argument.isFlag() && argument.getFlag().getCode() < 4;
        }
    }

}
