var assert, deepEqual, throws, p;
assert = require('assert'), deepEqual = assert.deepEqual, throws = assert.throws;
p = require('../..').parseType;
describe('parse type', function(){
  it('simple', function(){
    return deepEqual(p('Number'), [{
      type: 'Number'
    }]);
  });
  it('different characters', function(){
    return deepEqual(p('2T_and$'), [{
      type: '2T_and$'
    }]);
  });
  it('Maybe', function(){
    deepEqual(p('Maybe Number'), [
      {
        type: 'Undefined'
      }, {
        type: 'Null'
      }, {
        type: 'Number'
      }
    ]);
    deepEqual(p('Maybe Null | Number'), [
      {
        type: 'Undefined'
      }, {
        type: 'Null'
      }, {
        type: 'Number'
      }
    ]);
    return deepEqual(p('Maybe Undefined | String'), [
      {
        type: 'Undefined'
      }, {
        type: 'Null'
      }, {
        type: 'String'
      }
    ]);
  });
  it('wildcard', function(){
    deepEqual(p('*'), [{
      type: '*'
    }]);
    deepEqual(p('[*]'), [{
      structure: 'array',
      of: [{
        type: '*'
      }]
    }]);
    deepEqual(p('{x: *}'), [{
      structure: 'fields',
      of: {
        x: [{
          type: '*'
        }]
      },
      subset: false
    }]);
    return deepEqual(p('*{a:Number}'), [{
      type: '*',
      structure: 'fields',
      of: {
        a: [{
          type: 'Number'
        }]
      },
      subset: false
    }]);
  });
  it('multiple types', function(){
    it('one', function(){
      return deepEqual(p('Number'), [{
        type: 'Number'
      }]);
    });
    it('two', function(){
      return deepEqual(p('Number | String'), [
        {
          type: 'Number'
        }, {
          type: 'String'
        }
      ]);
    });
    it('three', function(){
      return deepEqual(p('Number | String | Float'), [
        {
          type: 'Number'
        }, {
          type: 'String'
        }, {
          type: 'Float'
        }
      ]);
    });
    return it('two', function(){
      return deepEqual(p('Number | Number'), [{
        type: 'Number'
      }]);
    });
  });
  it('optional comment', function(){
    it('basic', function(){
      return deepEqual(p('x :: Number'), [{
        type: 'Number'
      }]);
    });
    it('multiple', function(){
      return deepEqual(p('x :: Number | String'), [
        {
          type: 'Number'
        }, {
          type: 'String'
        }
      ]);
    });
    it('structures', function(){
      deepEqual(p('list :: [Number]'), [{
        structure: 'array',
        of: [{
          type: 'Number'
        }]
      }]);
      return deepEqual(p('[element :: Number]'), [{
        structure: 'array',
        of: [{
          type: 'Number'
        }]
      }]);
    });
    return it('no comment specified', function(){
      return throws(function(){
        return p(':: Number');
      }, /No comment before comment separator '::' found/);
    });
  });
  it('array structure', function(){
    it('simple', function(){
      return deepEqual(p('[Number]'), [{
        structure: 'array',
        of: [{
          type: 'Number'
        }]
      }]);
    });
    return it('nested', function(){
      return deepEqual(p('[[Number]]'), [{
        structure: 'array',
        of: [{
          structure: 'array',
          of: [{
            type: 'Number'
          }]
        }]
      }]);
    });
  });
  it('array structure with type', function(){
    it('simple', function(){
      return deepEqual(p('Int16Array[Int]'), [{
        type: 'Int16Array',
        structure: 'array',
        of: [{
          type: 'Int'
        }]
      }]);
    });
    return it('nested', function(){
      return deepEqual(p('Array[Float32Array[Float]]'), [{
        type: 'Array',
        structure: 'array',
        of: [{
          type: 'Float32Array',
          structure: 'array',
          of: [{
            type: 'Float'
          }]
        }]
      }]);
    });
  });
  it('tuple structure', function(){
    it('single', function(){
      return deepEqual(p('(Number)'), [{
        structure: 'tuple',
        of: [[{
          type: 'Number'
        }]]
      }]);
    });
    it('double', function(){
      return deepEqual(p('(Number, String)'), [{
        structure: 'tuple',
        of: [
          [{
            type: 'Number'
          }], [{
            type: 'String'
          }]
        ]
      }]);
    });
    it('trailing comma', function(){
      return deepEqual(p('(Number, String,)'), [{
        structure: 'tuple',
        of: [
          [{
            type: 'Number'
          }], [{
            type: 'String'
          }]
        ]
      }]);
    });
    return it('nested', function(){
      return deepEqual(p('((Number, String), (Float))'), [{
        structure: 'tuple',
        of: [
          [{
            structure: 'tuple',
            of: [
              [{
                type: 'Number'
              }], [{
                type: 'String'
              }]
            ]
          }], [{
            structure: 'tuple',
            of: [[{
              type: 'Float'
            }]]
          }]
        ]
      }]);
    });
  });
  it('tuple structure with type', function(){
    it('double', function(){
      return deepEqual(p('Type(Number, String)'), [{
        type: 'Type',
        structure: 'tuple',
        of: [
          [{
            type: 'Number'
          }], [{
            type: 'String'
          }]
        ]
      }]);
    });
    return it('nested', function(){
      return deepEqual(p('Type(Type2(Number, String), Type3(Float))'), [{
        type: 'Type',
        structure: 'tuple',
        of: [
          [{
            type: 'Type2',
            structure: 'tuple',
            of: [
              [{
                type: 'Number'
              }], [{
                type: 'String'
              }]
            ]
          }], [{
            type: 'Type3',
            structure: 'tuple',
            of: [[{
              type: 'Float'
            }]]
          }]
        ]
      }]);
    });
  });
  it('fields structure, without type', function(){
    it('simple', function(){
      return deepEqual(p('{a:Number, b:String}'), [{
        structure: 'fields',
        of: {
          a: [{
            type: 'Number'
          }],
          b: [{
            type: 'String'
          }]
        },
        subset: false
      }]);
    });
    it('trailing comma', function(){
      return deepEqual(p('{a:Number, b:String,}'), [{
        structure: 'fields',
        of: {
          a: [{
            type: 'Number'
          }],
          b: [{
            type: 'String'
          }]
        },
        subset: false
      }]);
    });
    it('nested', function(){
      return deepEqual(p('{a: {message: String}, b:String}'), [{
        structure: 'fields',
        of: {
          a: [{
            structure: 'fields',
            of: {
              message: [{
                type: 'String'
              }]
            },
            subset: false
          }],
          b: [{
            type: 'String'
          }]
        },
        subset: false
      }]);
    });
    it('subset', function(){
      return deepEqual(p('{a:Number, ...}'), [{
        structure: 'fields',
        of: {
          a: [{
            type: 'Number'
          }]
        },
        subset: true
      }]);
    });
    return it('no fields specified', function(){
      return deepEqual(p('{...}'), [{
        structure: 'fields',
        of: {},
        subset: true
      }]);
    });
  });
  it('fields structure, with type', function(){
    it('simple', function(){
      return deepEqual(p('Object{a:Number, b:String}'), [{
        type: 'Object',
        structure: 'fields',
        of: {
          a: [{
            type: 'Number'
          }],
          b: [{
            type: 'String'
          }]
        },
        subset: false
      }]);
    });
    it('nested', function(){
      return deepEqual(p('Object{a: Error{message: String}, b:String}'), [{
        type: 'Object',
        structure: 'fields',
        of: {
          a: [{
            type: 'Error',
            structure: 'fields',
            of: {
              message: [{
                type: 'String'
              }]
            },
            subset: false
          }],
          b: [{
            type: 'String'
          }]
        },
        subset: false
      }]);
    });
    it('subset', function(){
      return deepEqual(p('Node{a:Number, ...}'), [{
        type: 'Node',
        structure: 'fields',
        of: {
          a: [{
            type: 'Number'
          }]
        },
        subset: true
      }]);
    });
    return it('no fields specified', function(){
      return deepEqual(p('Date{...}'), [{
        type: 'Date',
        structure: 'fields',
        of: {},
        subset: true
      }]);
    });
  });
  it('errors', function(){
    it('no type specified', function(){
      return throws(function(){
        return p('');
      }, /No type specified/);
    });
    it('tuple of length 0', function(){
      return throws(function(){
        return p('()');
      }, /Tuple must be of at least length 1/);
    });
    it('array without type', function(){
      return throws(function(){
        return p('[]');
      }, /Must specify type of Array/);
    });
    it('unexpected end of input', function(){
      throws(function(){
        return p(' ');
      }, /Unexpected end of input/);
      throws(function(){
        return p('[');
      }, /Unexpected end of input/);
      throws(function(){
        return p('[Number');
      }, /Unexpected end of input/);
      return throws(function(){
        return p('{');
      }, /Unexpected end of input/);
    });
    it('unexpected end of input (input never tokenized)', function(){
      return throws(function(){
        return p('{Number:');
      }, /Unexpected end of input/);
    });
    it('unexpected character', function(){
      throws(function(){
        return p('[)');
      }, /Unexpected character: \)/);
      return throws(function(){
        return p('^');
      }, /Unexpected character: \^/);
    });
    it('function types not supported', function(){
      return throws(function(){
        return p('Number -> String');
      }, /Function types are not supported. To validate that something is a function, you may use 'Function'/);
    });
    it('expected op', function(){
      return throws(function(){
        return p('[Number)');
      }, /Expected '\]', got '\)' instead/);
    });
    return it('expected text', function(){
      return throws(function(){
        return p('{:Number}');
      }, /Expected text, got ':' instead/);
    });
  });
});
